Custom Emoji: Dynamically update color (#2681)
This commit is contained in:
parent
12810e8200
commit
2b6e3b3f8c
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState,
|
memo, useCallback, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../global';
|
import { getGlobal } from '../../global';
|
||||||
|
|
||||||
@ -7,13 +7,12 @@ import type { FC, TeactNode } from '../../lib/teact/teact';
|
|||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
import { ApiMessageEntityTypes } from '../../api/types';
|
import { ApiMessageEntityTypes } from '../../api/types';
|
||||||
|
|
||||||
import { getPropertyHexColor } from '../../util/themeStyle';
|
|
||||||
import { hexToRgb } from '../../util/switchTheme';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import safePlay from '../../util/safePlay';
|
import safePlay from '../../util/safePlay';
|
||||||
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
||||||
|
|
||||||
import useCustomEmoji from './hooks/useCustomEmoji';
|
import useCustomEmoji from './hooks/useCustomEmoji';
|
||||||
|
import useDynamicColorListener from '../../hooks/useDynamicColorListener';
|
||||||
|
|
||||||
import StickerView from './StickerView';
|
import StickerView from './StickerView';
|
||||||
|
|
||||||
@ -75,22 +74,8 @@ const CustomEmoji: FC<OwnProps> = ({
|
|||||||
const loopCountRef = useRef(0);
|
const loopCountRef = useRef(0);
|
||||||
const [shouldLoop, setShouldLoop] = useState(true);
|
const [shouldLoop, setShouldLoop] = useState(true);
|
||||||
|
|
||||||
const [customColor, setCustomColor] = useState<[number, number, number] | undefined>();
|
|
||||||
const hasCustomColor = customEmoji?.shouldUseTextColor;
|
const hasCustomColor = customEmoji?.shouldUseTextColor;
|
||||||
|
const { rgbColor: customColor } = useDynamicColorListener(containerRef, !hasCustomColor);
|
||||||
useEffect(() => {
|
|
||||||
if (!hasCustomColor) {
|
|
||||||
setCustomColor(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const hexColor = getPropertyHexColor(getComputedStyle(containerRef.current!), '--color-text');
|
|
||||||
if (!hexColor) {
|
|
||||||
setCustomColor(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const customColorRgb = hexToRgb(hexColor);
|
|
||||||
setCustomColor([customColorRgb.r, customColorRgb.g, customColorRgb.b]);
|
|
||||||
}, [hasCustomColor]);
|
|
||||||
|
|
||||||
const handleVideoEnded = useCallback((e) => {
|
const handleVideoEnded = useCallback((e) => {
|
||||||
if (!loopLimit) return;
|
if (!loopLimit) return;
|
||||||
|
|||||||
@ -28,9 +28,10 @@
|
|||||||
|
|
||||||
.VerifiedIcon,
|
.VerifiedIcon,
|
||||||
.PremiumIcon {
|
.PremiumIcon {
|
||||||
z-index: 2;
|
|
||||||
--color-fill: var(--color-white);
|
--color-fill: var(--color-white);
|
||||||
--color-checkmark: var(--color-primary);
|
--color-checkmark: var(--color-primary);
|
||||||
|
|
||||||
|
z-index: 2;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +42,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-emoji {
|
.custom-emoji {
|
||||||
|
--custom-emoji-size: 1.5rem;
|
||||||
|
|
||||||
|
color: var(--color-white);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
--custom-emoji-size: 1.5rem;
|
|
||||||
--color-text: var(--color-white);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo, useCallback, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -9,8 +9,6 @@ import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import { getPropertyHexColor } from '../../util/themeStyle';
|
|
||||||
import { hexToRgb } from '../../util/switchTheme';
|
|
||||||
import { getServerTimeOffset } from '../../util/serverTime';
|
import { getServerTimeOffset } from '../../util/serverTime';
|
||||||
|
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
@ -18,6 +16,7 @@ import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useContextMenuPosition from '../../hooks/useContextMenuPosition';
|
import useContextMenuPosition from '../../hooks/useContextMenuPosition';
|
||||||
|
import useDynamicColorListener from '../../hooks/useDynamicColorListener';
|
||||||
|
|
||||||
import StickerView from './StickerView';
|
import StickerView from './StickerView';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
@ -87,22 +86,8 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const [customColor, setCustomColor] = useState<[number, number, number] | undefined>();
|
|
||||||
const hasCustomColor = sticker.shouldUseTextColor;
|
const hasCustomColor = sticker.shouldUseTextColor;
|
||||||
|
const { rgbColor: customColor } = useDynamicColorListener(ref, !hasCustomColor);
|
||||||
useEffect(() => {
|
|
||||||
if (!hasCustomColor) {
|
|
||||||
setCustomColor(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const hexColor = getPropertyHexColor(getComputedStyle(ref.current!), '--color-text');
|
|
||||||
if (!hexColor) {
|
|
||||||
setCustomColor(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const customColorRgb = hexToRgb(hexColor);
|
|
||||||
setCustomColor([customColorRgb.r, customColorRgb.g, customColorRgb.b]);
|
|
||||||
}, [hasCustomColor]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id, isCustomEmoji, hasEffect: isPremium, stickerSetInfo,
|
id, isCustomEmoji, hasEffect: isPremium, stickerSetInfo,
|
||||||
|
|||||||
@ -73,8 +73,8 @@
|
|||||||
&.selected:not(.forum):hover {
|
&.selected:not(.forum):hover {
|
||||||
--background-color: var(--color-chat-active) !important;
|
--background-color: var(--color-chat-active) !important;
|
||||||
|
|
||||||
.custom-emoji {
|
.title .custom-emoji {
|
||||||
--color-text: var(--color-white);
|
color: var(--color-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.VerifiedIcon, .PremiumIcon {
|
.VerifiedIcon, .PremiumIcon {
|
||||||
@ -192,10 +192,6 @@
|
|||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-emoji {
|
|
||||||
--color-text: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-badge-wrapper {
|
.avatar-badge-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -216,6 +212,10 @@
|
|||||||
.info {
|
.info {
|
||||||
transition: opacity 300ms ease, transform var(--layer-transition);
|
transition: opacity 300ms ease, transform var(--layer-transition);
|
||||||
|
|
||||||
|
.title .custom-emoji {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-muted {
|
.icon-muted {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
margin-top: -0.0625rem;
|
margin-top: -0.0625rem;
|
||||||
|
|||||||
@ -75,7 +75,6 @@ type StateProps = {
|
|||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
userStatus?: ApiUserStatus;
|
userStatus?: ApiUserStatus;
|
||||||
isEmojiStatusColored?: boolean;
|
|
||||||
actionTargetUserIds?: string[];
|
actionTargetUserIds?: string[];
|
||||||
actionTargetMessage?: ApiMessage;
|
actionTargetMessage?: ApiMessage;
|
||||||
actionTargetChatId?: string;
|
actionTargetChatId?: string;
|
||||||
@ -103,7 +102,6 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
isMuted,
|
isMuted,
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
isEmojiStatusColored,
|
|
||||||
actionTargetUserIds,
|
actionTargetUserIds,
|
||||||
lastMessageSender,
|
lastMessageSender,
|
||||||
lastMessageOutgoingStatus,
|
lastMessageOutgoingStatus,
|
||||||
@ -256,7 +254,6 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
withEmojiStatus
|
withEmojiStatus
|
||||||
isSavedMessages={chatId === user?.id && user?.isSelf}
|
isSavedMessages={chatId === user?.id && user?.isSelf}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
key={!isMobile && isEmojiStatusColored ? `${isSelected}` : undefined}
|
|
||||||
/>
|
/>
|
||||||
{isMuted && <i className="icon-muted" />}
|
{isMuted && <i className="icon-muted" />}
|
||||||
<div className="separator" />
|
<div className="separator" />
|
||||||
@ -331,8 +328,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
||||||
|
|
||||||
const statusEmoji = user?.emojiStatus && global.customEmojis.byId[user.emojiStatus.documentId];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||||
@ -354,7 +349,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
userStatus,
|
userStatus,
|
||||||
lastMessageTopic,
|
lastMessageTopic,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
isEmojiStatusColored: statusEmoji?.shouldUseTextColor,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Chat));
|
)(Chat));
|
||||||
|
|||||||
@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
.emoji-status {
|
.emoji-status {
|
||||||
--custom-emoji-size: 1.5rem;
|
--custom-emoji-size: 1.5rem;
|
||||||
--color-text: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.PremiumIcon {
|
.PremiumIcon {
|
||||||
|
|||||||
@ -14,4 +14,8 @@
|
|||||||
right: 0.5rem !important;
|
right: 0.5rem !important;
|
||||||
width: calc(100vw - 1rem);
|
width: calc(100vw - 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.custom-emoji) {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -320,7 +320,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-emoji {
|
.custom-emoji {
|
||||||
--color-text: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import { REM } from '../../../common/helpers/mediaDimensions';
|
|||||||
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
||||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||||
import useThrottledCallback from '../../../../hooks/useThrottledCallback';
|
import useThrottledCallback from '../../../../hooks/useThrottledCallback';
|
||||||
|
import useDynamicColorListener from '../../../../hooks/useDynamicColorListener';
|
||||||
|
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
|
||||||
|
|
||||||
const SIZE = 1.25 * REM;
|
const SIZE = 1.25 * REM;
|
||||||
const THROTTLE_MS = 300;
|
const THROTTLE_MS = 300;
|
||||||
@ -41,6 +43,7 @@ export default function useInputCustomEmojis(
|
|||||||
prefixId?: string,
|
prefixId?: string,
|
||||||
isActive?: boolean,
|
isActive?: boolean,
|
||||||
) {
|
) {
|
||||||
|
const { rgbColor: textColor } = useDynamicColorListener(inputRef);
|
||||||
const mapRef = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
const mapRef = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
||||||
|
|
||||||
const removeContainers = useCallback((ids: string[]) => {
|
const removeContainers = useCallback((ids: string[]) => {
|
||||||
@ -60,11 +63,12 @@ export default function useInputCustomEmojis(
|
|||||||
const customEmojies = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji'));
|
const customEmojies = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji'));
|
||||||
|
|
||||||
customEmojies.forEach((element) => {
|
customEmojies.forEach((element) => {
|
||||||
const id = `${prefixId || ''}${element.dataset.uniqueId!}`;
|
if (!element.dataset.uniqueId) {
|
||||||
const documentId = element.dataset.documentId!;
|
|
||||||
if (!id) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const id = `${prefixId || ''}${element.dataset.uniqueId}${textColor?.join(',') || ''}`;
|
||||||
|
const documentId = element.dataset.documentId!;
|
||||||
|
|
||||||
removedContainers.delete(id);
|
removedContainers.delete(id);
|
||||||
|
|
||||||
const mediaUrl = getCustomEmojiMediaDataForInput(documentId);
|
const mediaUrl = getCustomEmojiMediaDataForInput(documentId);
|
||||||
@ -95,9 +99,11 @@ export default function useInputCustomEmojis(
|
|||||||
sharedCanvasHqRef,
|
sharedCanvasHqRef,
|
||||||
absoluteContainerRef,
|
absoluteContainerRef,
|
||||||
uniqueId: id,
|
uniqueId: id,
|
||||||
|
containerId: prefixId || id,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
isHq,
|
isHq,
|
||||||
position: { x, y },
|
position: { x, y },
|
||||||
|
textColor,
|
||||||
});
|
});
|
||||||
animation.play();
|
animation.play();
|
||||||
|
|
||||||
@ -105,7 +111,7 @@ export default function useInputCustomEmojis(
|
|||||||
});
|
});
|
||||||
|
|
||||||
removeContainers(Array.from(removedContainers));
|
removeContainers(Array.from(removedContainers));
|
||||||
}, [absoluteContainerRef, inputRef, prefixId, removeContainers, sharedCanvasHqRef, sharedCanvasRef]);
|
}, [absoluteContainerRef, textColor, inputRef, prefixId, removeContainers, sharedCanvasHqRef, sharedCanvasRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addCustomEmojiInputRenderCallback(synchronizeElements);
|
addCustomEmojiInputRenderCallback(synchronizeElements);
|
||||||
@ -127,6 +133,12 @@ export default function useInputCustomEmojis(
|
|||||||
});
|
});
|
||||||
}, [getHtml, synchronizeElements, inputRef, removeContainers, sharedCanvasRef, isActive]);
|
}, [getHtml, synchronizeElements, inputRef, removeContainers, sharedCanvasRef, isActive]);
|
||||||
|
|
||||||
|
useEffectWithPrevDeps(([prevTextColor]) => {
|
||||||
|
if (textColor !== prevTextColor) {
|
||||||
|
synchronizeElements();
|
||||||
|
}
|
||||||
|
}, [textColor, synchronizeElements]);
|
||||||
|
|
||||||
const throttledSynchronizeElements = useThrottledCallback(
|
const throttledSynchronizeElements = useThrottledCallback(
|
||||||
synchronizeElements,
|
synchronizeElements,
|
||||||
[synchronizeElements],
|
[synchronizeElements],
|
||||||
@ -163,32 +175,38 @@ function createPlayer({
|
|||||||
sharedCanvasHqRef,
|
sharedCanvasHqRef,
|
||||||
absoluteContainerRef,
|
absoluteContainerRef,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
|
containerId,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
position,
|
position,
|
||||||
isHq,
|
isHq,
|
||||||
|
textColor,
|
||||||
}: {
|
}: {
|
||||||
customEmoji: ApiSticker;
|
customEmoji: ApiSticker;
|
||||||
sharedCanvasRef: React.RefObject<HTMLCanvasElement>;
|
sharedCanvasRef: React.RefObject<HTMLCanvasElement>;
|
||||||
sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>;
|
sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>;
|
||||||
absoluteContainerRef: React.RefObject<HTMLElement>;
|
absoluteContainerRef: React.RefObject<HTMLElement>;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
|
containerId: string;
|
||||||
mediaUrl: string;
|
mediaUrl: string;
|
||||||
position: { x: number; y: number };
|
position: { x: number; y: number };
|
||||||
isHq?: boolean;
|
isHq?: boolean;
|
||||||
|
textColor?: [number, number, number];
|
||||||
}): CustomEmojiPlayer {
|
}): CustomEmojiPlayer {
|
||||||
if (customEmoji.isLottie) {
|
if (customEmoji.isLottie) {
|
||||||
const lottie = RLottie.init(
|
const lottie = RLottie.init(
|
||||||
uniqueId,
|
containerId,
|
||||||
isHq ? sharedCanvasHqRef.current! : sharedCanvasRef.current!,
|
isHq ? sharedCanvasHqRef.current! : sharedCanvasRef.current!,
|
||||||
undefined,
|
undefined,
|
||||||
customEmoji.id,
|
uniqueId,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
{
|
{
|
||||||
size: SIZE,
|
size: SIZE,
|
||||||
coords: position,
|
coords: position,
|
||||||
isLowPriority: !isHq,
|
isLowPriority: !isHq,
|
||||||
},
|
},
|
||||||
|
customEmoji.shouldUseTextColor ? textColor : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
play: () => lottie.play(),
|
play: () => lottie.play(),
|
||||||
pause: () => lottie.pause(),
|
pause: () => lottie.pause(),
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.particle {
|
.particle {
|
||||||
--color-text: var(--color-primary);
|
color: var(--color-primary);
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
|||||||
@ -281,7 +281,7 @@
|
|||||||
color: var(--color-user-#{$i});
|
color: var(--color-user-#{$i});
|
||||||
|
|
||||||
.custom-emoji {
|
.custom-emoji {
|
||||||
--color-text: var(--color-user-#{$i});
|
color: var(--color-user-#{$i});
|
||||||
}
|
}
|
||||||
|
|
||||||
.PremiumIcon {
|
.PremiumIcon {
|
||||||
|
|||||||
70
src/hooks/useDynamicColorListener.ts
Normal file
70
src/hooks/useDynamicColorListener.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
useCallback, useEffect, useRef, useState,
|
||||||
|
} from '../lib/teact/teact';
|
||||||
|
import { hexToRgb } from '../util/switchTheme';
|
||||||
|
import { getPropertyHexColor } from '../util/themeStyle';
|
||||||
|
import useResizeObserver from './useResizeObserver';
|
||||||
|
import useSyncEffect from './useSyncEffect';
|
||||||
|
|
||||||
|
// Delay required to prevent constant re-rendering on theme change
|
||||||
|
const TRANSITION_STYLE = '0.1s color linear 50ms';
|
||||||
|
|
||||||
|
export default function useDynamicColorListener(ref?: React.RefObject<HTMLElement>, isDisabled?: boolean) {
|
||||||
|
const [hexColor, setHexColor] = useState<string | undefined>();
|
||||||
|
const rgbColorRef = useRef<[number, number, number] | undefined>();
|
||||||
|
|
||||||
|
const updateColor = useCallback(() => {
|
||||||
|
if (!ref?.current || isDisabled) {
|
||||||
|
setHexColor(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentHexColor = getPropertyHexColor(getComputedStyle(ref.current), 'color');
|
||||||
|
setHexColor(currentHexColor);
|
||||||
|
}, [isDisabled, ref]);
|
||||||
|
|
||||||
|
// Element does not receive `transitionend` event if parent has `display: none`.
|
||||||
|
// We will receive `resize` event when parent is shown again.
|
||||||
|
useResizeObserver(!isDisabled ? ref : undefined, updateColor);
|
||||||
|
|
||||||
|
// Update RGB color only when hex color changes
|
||||||
|
useSyncEffect(() => {
|
||||||
|
if (!hexColor) {
|
||||||
|
rgbColorRef.current = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { r, g, b } = hexToRgb(hexColor);
|
||||||
|
rgbColorRef.current = [r, g, b];
|
||||||
|
}, [hexColor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref?.current) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColor();
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTransitionEnd(e: TransitionEvent) {
|
||||||
|
if (e.propertyName !== 'color') return;
|
||||||
|
updateColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = ref.current;
|
||||||
|
el.addEventListener('transitionend', handleTransitionEnd);
|
||||||
|
el.style.setProperty('transition', TRANSITION_STYLE, 'important');
|
||||||
|
return () => {
|
||||||
|
el.removeEventListener('transitionend', handleTransitionEnd);
|
||||||
|
el.style.removeProperty('transition');
|
||||||
|
};
|
||||||
|
}, [isDisabled, ref, updateColor]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hexColor,
|
||||||
|
rgbColor: rgbColorRef.current,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user