Message: Support sticky avatar (#5360)
This commit is contained in:
parent
8a8aea1b4d
commit
d00230ad33
@ -92,13 +92,13 @@ export function useStickerPickerObservers(
|
|||||||
const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!;
|
const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!;
|
||||||
const isClose = Math.abs(currentIndex - index) === 1;
|
const isClose = Math.abs(currentIndex - index) === 1;
|
||||||
|
|
||||||
animateScroll(
|
animateScroll({
|
||||||
containerRef.current!,
|
container: containerRef.current!,
|
||||||
stickerSetEl,
|
element: stickerSetEl,
|
||||||
'start',
|
position: 'start',
|
||||||
FOCUS_MARGIN,
|
margin: FOCUS_MARGIN,
|
||||||
isClose ? SCROLL_MAX_DISTANCE_WHEN_CLOSE : SCROLL_MAX_DISTANCE_WHEN_FAR,
|
maxDistance: isClose ? SCROLL_MAX_DISTANCE_WHEN_CLOSE : SCROLL_MAX_DISTANCE_WHEN_FAR,
|
||||||
);
|
});
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -97,7 +97,12 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
animateScroll(messagesContainer, lastMessageElement, 'end', FOCUS_MARGIN);
|
animateScroll({
|
||||||
|
container: messagesContainer,
|
||||||
|
element: lastMessageElement,
|
||||||
|
position: 'end',
|
||||||
|
margin: FOCUS_MARGIN,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -555,16 +555,13 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
// Break out of `forceLayout`
|
// Break out of `forceLayout`
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
const shouldScrollToBottom = !isBackgroundModeActive() || !firstUnreadElement;
|
const shouldScrollToBottom = !isBackgroundModeActive() || !firstUnreadElement;
|
||||||
|
animateScroll({
|
||||||
animateScroll(
|
|
||||||
container,
|
container,
|
||||||
shouldScrollToBottom ? lastItemElement! : firstUnreadElement!,
|
element: shouldScrollToBottom ? lastItemElement! : firstUnreadElement!,
|
||||||
shouldScrollToBottom ? 'end' : 'start',
|
position: shouldScrollToBottom ? 'end' : 'start',
|
||||||
BOTTOM_FOCUS_MARGIN,
|
margin: BOTTOM_FOCUS_MARGIN,
|
||||||
undefined,
|
forceDuration: noMessageSendingAnimation ? 0 : undefined,
|
||||||
undefined,
|
});
|
||||||
noMessageSendingAnimation ? 0 : undefined,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import useScrollHooks from './hooks/useScrollHooks';
|
|||||||
|
|
||||||
import ActionMessage from './ActionMessage';
|
import ActionMessage from './ActionMessage';
|
||||||
import Message from './message/Message';
|
import Message from './message/Message';
|
||||||
|
import SenderGroupContainer from './message/SenderGroupContainer';
|
||||||
import SponsoredMessage from './message/SponsoredMessage';
|
import SponsoredMessage from './message/SponsoredMessage';
|
||||||
import MessageListBotInfo from './MessageListBotInfo';
|
import MessageListBotInfo from './MessageListBotInfo';
|
||||||
|
|
||||||
@ -142,12 +143,10 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
messageIds && prevMessageIds && messageIds[messageIds.length - 2] === prevMessageIds[prevMessageIds.length - 1],
|
messageIds && prevMessageIds && messageIds[messageIds.length - 2] === prevMessageIds[prevMessageIds.length - 1],
|
||||||
);
|
);
|
||||||
|
|
||||||
const dateGroups = messageGroups.map((
|
function calculateSenderGroups(
|
||||||
dateGroup: MessageDateGroup,
|
dateGroup: MessageDateGroup, dateGroupIndex: number, dateGroupsArray: MessageDateGroup[],
|
||||||
dateGroupIndex: number,
|
) {
|
||||||
dateGroupsArray: MessageDateGroup[],
|
return dateGroup.senderGroups.map((
|
||||||
) => {
|
|
||||||
const senderGroups = dateGroup.senderGroups.map((
|
|
||||||
senderGroup,
|
senderGroup,
|
||||||
senderGroupIndex,
|
senderGroupIndex,
|
||||||
senderGroupsArray,
|
senderGroupsArray,
|
||||||
@ -186,7 +185,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
|
|
||||||
let currentDocumentGroupId: string | undefined;
|
let currentDocumentGroupId: string | undefined;
|
||||||
|
|
||||||
return senderGroup.map((
|
const senderGroupElements = senderGroup.map((
|
||||||
messageOrAlbum,
|
messageOrAlbum,
|
||||||
messageIndex,
|
messageIndex,
|
||||||
) => {
|
) => {
|
||||||
@ -260,7 +259,44 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}).flat();
|
}).flat();
|
||||||
|
|
||||||
|
if (!withUsers) return senderGroupElements;
|
||||||
|
|
||||||
|
const lastMessageOrAlbum = senderGroup[senderGroup.length - 1];
|
||||||
|
const lastMessage = isAlbum(lastMessageOrAlbum) ? lastMessageOrAlbum.mainMessage : lastMessageOrAlbum;
|
||||||
|
const lastMessageId = getMessageOriginalId(lastMessage);
|
||||||
|
|
||||||
|
const isTopicTopMessage = lastMessage.id === threadId;
|
||||||
|
const isOwn = isOwnMessage(lastMessage);
|
||||||
|
|
||||||
|
const firstMessageOrAlbum = senderGroup[0];
|
||||||
|
const firstMessage = isAlbum(firstMessageOrAlbum) ? firstMessageOrAlbum.mainMessage : firstMessageOrAlbum;
|
||||||
|
const firstMessageId = getMessageOriginalId(firstMessage);
|
||||||
|
|
||||||
|
const key = `${firstMessageId}-${lastMessageId}`;
|
||||||
|
const id = (firstMessageId === lastMessageId) ? `message-group-${firstMessageId}`
|
||||||
|
: `message-group-${firstMessageId}-${lastMessageId}`;
|
||||||
|
|
||||||
|
const withAvatar = withUsers && !isOwn && (!isTopicTopMessage || !isComments);
|
||||||
|
return (
|
||||||
|
<SenderGroupContainer
|
||||||
|
key={key}
|
||||||
|
id={id}
|
||||||
|
message={lastMessage}
|
||||||
|
withAvatar={withAvatar}
|
||||||
|
>
|
||||||
|
{senderGroupElements}
|
||||||
|
</SenderGroupContainer>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateGroups = messageGroups.map((
|
||||||
|
dateGroup: MessageDateGroup,
|
||||||
|
dateGroupIndex: number,
|
||||||
|
dateGroupsArray: MessageDateGroup[],
|
||||||
|
) => {
|
||||||
|
const senderGroups = calculateSenderGroups(dateGroup, dateGroupIndex, dateGroupsArray);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -174,7 +174,13 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
setActiveCategoryIndex(index);
|
setActiveCategoryIndex(index);
|
||||||
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
|
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
|
||||||
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
|
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
|
||||||
animateScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE);
|
animateScroll({
|
||||||
|
container: containerRef.current!,
|
||||||
|
element: categoryEl,
|
||||||
|
position: 'start',
|
||||||
|
margin: FOCUS_MARGIN,
|
||||||
|
maxDistance: SMOOTH_SCROLL_DISTANCE,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleEmojiSelect = useLastCallback((emoji: string, name: string) => {
|
const handleEmojiSelect = useLastCallback((emoji: string, name: string) => {
|
||||||
|
|||||||
@ -618,7 +618,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleAvatarClick,
|
|
||||||
handleSenderClick,
|
handleSenderClick,
|
||||||
handleViaBotClick,
|
handleViaBotClick,
|
||||||
handleReplyClick,
|
handleReplyClick,
|
||||||
@ -974,19 +973,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
contentWidth, noMediaCorners, style, reactionsMaxWidth,
|
contentWidth, noMediaCorners, style, reactionsMaxWidth,
|
||||||
} = sizeCalculations;
|
} = sizeCalculations;
|
||||||
|
|
||||||
function renderAvatar() {
|
|
||||||
const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
size="small"
|
|
||||||
peer={avatarPeer}
|
|
||||||
text={hiddenName}
|
|
||||||
onClick={avatarPeer ? handleAvatarClick : undefined}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMessageText(isForAnimation?: boolean) {
|
function renderMessageText(isForAnimation?: boolean) {
|
||||||
if (!textMessage) return undefined;
|
if (!textMessage) return undefined;
|
||||||
return (
|
return (
|
||||||
@ -1629,7 +1615,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{withAvatar && renderAvatar()}
|
|
||||||
<div
|
<div
|
||||||
className={buildClassName('message-content-wrapper',
|
className={buildClassName('message-content-wrapper',
|
||||||
contentClassName.includes('text') && 'can-select-text',
|
contentClassName.includes('text') && 'can-select-text',
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarContainer {
|
||||||
|
position: absolute;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform var(--select-transition);
|
||||||
|
|
||||||
|
:global(.select-mode-active) & {
|
||||||
|
transform: translateX(2.5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.senderAvatar {
|
||||||
|
position: sticky !important;
|
||||||
|
bottom: 0.25rem;
|
||||||
|
|
||||||
|
:global(.select-mode-active) & {
|
||||||
|
bottom: 5rem;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
bottom: 3.6875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/components/middle/message/SenderGroupContainer.tsx
Normal file
133
src/components/middle/message/SenderGroupContainer.tsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiMessage,
|
||||||
|
ApiPeer,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isAnonymousForwardsChat,
|
||||||
|
isAnonymousOwnMessage,
|
||||||
|
isSystemBot,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectForwardedSender,
|
||||||
|
selectIsChatWithSelf,
|
||||||
|
selectSender,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
|
||||||
|
import styles from './SenderGroupContainer.module.scss';
|
||||||
|
|
||||||
|
type OwnProps =
|
||||||
|
{
|
||||||
|
message: ApiMessage;
|
||||||
|
withAvatar?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
sender?: ApiPeer;
|
||||||
|
canShowSender: boolean;
|
||||||
|
originSender?: ApiPeer;
|
||||||
|
isChatWithSelf?: boolean;
|
||||||
|
isRepliesChat?: boolean;
|
||||||
|
isAnonymousForwards?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
||||||
|
message,
|
||||||
|
withAvatar,
|
||||||
|
children,
|
||||||
|
id,
|
||||||
|
sender,
|
||||||
|
canShowSender,
|
||||||
|
originSender,
|
||||||
|
isChatWithSelf,
|
||||||
|
isRepliesChat,
|
||||||
|
isAnonymousForwards,
|
||||||
|
}) => {
|
||||||
|
const { openChat } = getActions();
|
||||||
|
|
||||||
|
const { forwardInfo } = message;
|
||||||
|
|
||||||
|
const messageSender = canShowSender ? sender : undefined;
|
||||||
|
|
||||||
|
const shouldPreferOriginSender = forwardInfo
|
||||||
|
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
||||||
|
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
||||||
|
|
||||||
|
const handleAvatarClick = useLastCallback(() => {
|
||||||
|
if (!avatarPeer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openChat({ id: avatarPeer.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderAvatar() {
|
||||||
|
const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
size="small"
|
||||||
|
className={styles.senderAvatar}
|
||||||
|
peer={avatarPeer}
|
||||||
|
text={hiddenName}
|
||||||
|
onClick={avatarPeer ? handleAvatarClick : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = buildClassName(
|
||||||
|
'sender-group-container',
|
||||||
|
styles.root,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id} className={className}>
|
||||||
|
{withAvatar && (
|
||||||
|
<div className={styles.avatarContainer}>
|
||||||
|
{renderAvatar()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, ownProps): StateProps => {
|
||||||
|
const {
|
||||||
|
message, withAvatar,
|
||||||
|
} = ownProps;
|
||||||
|
const { chatId } = message;
|
||||||
|
|
||||||
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
|
const isSystemBotChat = isSystemBot(chatId);
|
||||||
|
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
||||||
|
|
||||||
|
const forceSenderName = !isChatWithSelf && isAnonymousOwnMessage(message);
|
||||||
|
const canShowSender = withAvatar || forceSenderName;
|
||||||
|
const sender = selectSender(global, message);
|
||||||
|
const originSender = selectForwardedSender(global, message);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sender,
|
||||||
|
canShowSender,
|
||||||
|
originSender,
|
||||||
|
isChatWithSelf,
|
||||||
|
isRepliesChat: isSystemBotChat,
|
||||||
|
isAnonymousForwards,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SenderGroupContainer));
|
||||||
@ -48,17 +48,19 @@ export default function useFocusMessage({
|
|||||||
const scrollPosition = scrollTargetPosition || isToBottom ? 'end' : 'centerOrTop';
|
const scrollPosition = scrollTargetPosition || isToBottom ? 'end' : 'centerOrTop';
|
||||||
|
|
||||||
const exec = () => {
|
const exec = () => {
|
||||||
const result = animateScroll(
|
const maxDistance = focusDirection !== undefined
|
||||||
messagesContainer,
|
? (isToBottom ? BOTTOM_FOCUS_OFFSET : RELOCATED_FOCUS_OFFSET) : undefined;
|
||||||
elementRef.current!,
|
|
||||||
scrollPosition,
|
const result = animateScroll({
|
||||||
FOCUS_MARGIN,
|
container: messagesContainer,
|
||||||
focusDirection !== undefined ? (isToBottom ? BOTTOM_FOCUS_OFFSET : RELOCATED_FOCUS_OFFSET) : undefined,
|
element: elementRef.current!,
|
||||||
focusDirection,
|
position: scrollPosition,
|
||||||
undefined,
|
margin: FOCUS_MARGIN,
|
||||||
isResizingContainer,
|
maxDistance,
|
||||||
true,
|
forceDirection: focusDirection,
|
||||||
);
|
forceNormalContainerHeight: isResizingContainer,
|
||||||
|
shouldReturnMutationFn: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (isQuote) {
|
if (isQuote) {
|
||||||
const firstQuote = elementRef.current!.querySelector<HTMLSpanElement>('.is-quote');
|
const firstQuote = elementRef.current!.querySelector<HTMLSpanElement>('.is-quote');
|
||||||
|
|||||||
@ -23,7 +23,6 @@ export default function useInnerHandlers({
|
|||||||
asForwarded,
|
asForwarded,
|
||||||
isScheduled,
|
isScheduled,
|
||||||
album,
|
album,
|
||||||
avatarPeer,
|
|
||||||
senderPeer,
|
senderPeer,
|
||||||
botSender,
|
botSender,
|
||||||
messageTopic,
|
messageTopic,
|
||||||
@ -66,14 +65,6 @@ export default function useInnerHandlers({
|
|||||||
replyToMsgId, replyToPeerId, replyToTopId, isQuote, quoteText,
|
replyToMsgId, replyToPeerId, replyToTopId, isQuote, quoteText,
|
||||||
} = getMessageReplyInfo(message) || {};
|
} = getMessageReplyInfo(message) || {};
|
||||||
|
|
||||||
const handleAvatarClick = useLastCallback(() => {
|
|
||||||
if (!avatarPeer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openChat({ id: avatarPeer.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSenderClick = useLastCallback(() => {
|
const handleSenderClick = useLastCallback(() => {
|
||||||
if (!senderPeer) {
|
if (!senderPeer) {
|
||||||
showNotification({ message: lang('HidAccount') });
|
showNotification({ message: lang('HidAccount') });
|
||||||
@ -255,7 +246,6 @@ export default function useInnerHandlers({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleAvatarClick,
|
|
||||||
handleSenderClick,
|
handleSenderClick,
|
||||||
handleViaBotClick,
|
handleViaBotClick,
|
||||||
handleReplyClick,
|
handleReplyClick,
|
||||||
|
|||||||
@ -33,7 +33,12 @@ export default function useProfileState(
|
|||||||
if (container.scrollTop < tabsEl.offsetTop) {
|
if (container.scrollTop < tabsEl.offsetTop) {
|
||||||
onProfileStateChange(getStateFromTabType(tabType));
|
onProfileStateChange(getStateFromTabType(tabType));
|
||||||
isScrollingProgrammatically = true;
|
isScrollingProgrammatically = true;
|
||||||
animateScroll(container, tabsEl, 'start', undefined, undefined, undefined, TRANSITION_DURATION);
|
animateScroll({
|
||||||
|
container,
|
||||||
|
element: tabsEl,
|
||||||
|
position: 'start',
|
||||||
|
forceDuration: TRANSITION_DURATION,
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isScrollingProgrammatically = false;
|
isScrollingProgrammatically = false;
|
||||||
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
||||||
@ -59,13 +64,13 @@ export default function useProfileState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isScrollingProgrammatically = true;
|
isScrollingProgrammatically = true;
|
||||||
animateScroll(
|
|
||||||
|
animateScroll({
|
||||||
container,
|
container,
|
||||||
container.firstElementChild as HTMLElement,
|
element: container.firstElementChild as HTMLElement,
|
||||||
'start',
|
position: 'start',
|
||||||
undefined,
|
maxDistance: container.offsetHeight * 2,
|
||||||
container.offsetHeight * 2,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isScrollingProgrammatically = false;
|
isScrollingProgrammatically = false;
|
||||||
|
|||||||
@ -15,19 +15,27 @@ import { selectCanAnimateInterface } from '../global/selectors';
|
|||||||
import { animateSingle, cancelSingleAnimation } from './animation';
|
import { animateSingle, cancelSingleAnimation } from './animation';
|
||||||
import { IS_ANDROID } from './windowEnvironment';
|
import { IS_ANDROID } from './windowEnvironment';
|
||||||
|
|
||||||
type Params = Parameters<typeof createMutateFunction>;
|
export type AnimateScrollArgs = {
|
||||||
|
container: HTMLElement;
|
||||||
|
element: HTMLElement;
|
||||||
|
position: ScrollTargetPosition;
|
||||||
|
margin?: number;
|
||||||
|
maxDistance?: number;
|
||||||
|
forceDirection?: FocusDirection;
|
||||||
|
forceDuration?: number;
|
||||||
|
forceNormalContainerHeight?: boolean;
|
||||||
|
shouldReturnMutationFn?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
let isAnimating = false;
|
let isAnimating = false;
|
||||||
let currentArgs: Parameters<typeof createMutateFunction> | undefined;
|
let currentArgs: AnimateScrollArgs | undefined;
|
||||||
let onHeavyAnimationEnd: NoneToVoidFunction | undefined;
|
let onHeavyAnimationEnd: NoneToVoidFunction | undefined;
|
||||||
|
|
||||||
export default function animateScroll(...args: Params | [...Params, boolean]) {
|
export default function animateScroll(args: AnimateScrollArgs) {
|
||||||
currentArgs = args.slice(0, 8) as Params;
|
currentArgs = args;
|
||||||
|
const mutate = createMutateFunction(args);
|
||||||
|
|
||||||
const mutate = createMutateFunction(...currentArgs);
|
if (args.shouldReturnMutationFn) {
|
||||||
|
|
||||||
const shouldReturnMutationFn = args[8];
|
|
||||||
if (shouldReturnMutationFn) {
|
|
||||||
return mutate;
|
return mutate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,20 +51,39 @@ export function restartCurrentScrollAnimation() {
|
|||||||
cancelSingleAnimation();
|
cancelSingleAnimation();
|
||||||
|
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
requestMutation(createMutateFunction(...currentArgs!));
|
requestMutation(createMutateFunction(currentArgs!));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMutateFunction(
|
function getOffsetToContainer(element: HTMLElement, container: HTMLElement) {
|
||||||
container: HTMLElement,
|
let offsetTop = 0;
|
||||||
element: HTMLElement,
|
let offsetLeft = 0;
|
||||||
position: ScrollTargetPosition,
|
|
||||||
margin = 0,
|
let current: HTMLElement | null = element;
|
||||||
maxDistance = SCROLL_MAX_DISTANCE,
|
|
||||||
forceDirection?: FocusDirection,
|
while (current && current !== container && !current.contains(container)) {
|
||||||
forceDuration?: number,
|
offsetTop += current.offsetTop;
|
||||||
forceNormalContainerHeight?: boolean,
|
offsetLeft += current.offsetLeft;
|
||||||
) {
|
|
||||||
|
current = current.offsetParent as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { top: offsetTop, left: offsetLeft };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMutateFunction(args: AnimateScrollArgs) {
|
||||||
|
const {
|
||||||
|
container,
|
||||||
|
element,
|
||||||
|
position,
|
||||||
|
margin = 0,
|
||||||
|
maxDistance = SCROLL_MAX_DISTANCE,
|
||||||
|
forceDirection,
|
||||||
|
forceNormalContainerHeight,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
let forceDuration = args.forceDuration;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
forceDirection === FocusDirection.Static
|
forceDirection === FocusDirection.Static
|
||||||
|| !selectCanAnimateInterface(getGlobal())
|
|| !selectCanAnimateInterface(getGlobal())
|
||||||
@ -64,8 +91,10 @@ function createMutateFunction(
|
|||||||
forceDuration = 0;
|
forceDuration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { offsetTop: elementTop, offsetHeight: elementHeight } = element;
|
const { offsetHeight: elementHeight } = element;
|
||||||
const { scrollTop: currentScrollTop, offsetHeight: containerHeight, scrollHeight } = container;
|
const { scrollTop: currentScrollTop, offsetHeight: containerHeight, scrollHeight } = container;
|
||||||
|
const elementTop = getOffsetToContainer(element, container).top;
|
||||||
|
|
||||||
const targetContainerHeight = forceNormalContainerHeight && container.dataset.normalHeight
|
const targetContainerHeight = forceNormalContainerHeight && container.dataset.normalHeight
|
||||||
? Number(container.dataset.normalHeight)
|
? Number(container.dataset.normalHeight)
|
||||||
: containerHeight;
|
: containerHeight;
|
||||||
|
|||||||
@ -25,6 +25,11 @@ export default function setTooltipItemVisible(selector: string, index: number, c
|
|||||||
if (!visibleIndexes.includes(index)
|
if (!visibleIndexes.includes(index)
|
||||||
|| (index === first && !isFullyVisible(container, allElements[first]))) {
|
|| (index === first && !isFullyVisible(container, allElements[first]))) {
|
||||||
const position = index > visibleIndexes[visibleIndexes.length - 1] ? 'start' : 'end';
|
const position = index > visibleIndexes[visibleIndexes.length - 1] ? 'start' : 'end';
|
||||||
animateScroll(container, allElements[index], position, SCROLL_MARGIN);
|
animateScroll({
|
||||||
|
container,
|
||||||
|
element: allElements[index],
|
||||||
|
position,
|
||||||
|
margin: SCROLL_MARGIN,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user