From 3e48f3fbf9af188e583c1cf63669d4f58f042019 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 11 Sep 2024 02:07:15 +0200 Subject: [PATCH] `useShowTransitions`: Workaround for `noMountTransition` not working after forced reflow --- src/hooks/useShowTransition.ts | 29 ++++++++++++++++++++--------- src/lib/teact/teact-dom.ts | 1 + src/lib/teact/teact.ts | 16 ++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/hooks/useShowTransition.ts b/src/hooks/useShowTransition.ts index c68b77323..97c579999 100644 --- a/src/hooks/useShowTransition.ts +++ b/src/hooks/useShowTransition.ts @@ -1,4 +1,4 @@ -import type { RefObject } from 'react'; +import type { RefObject } from '../lib/teact/teact'; import { useLayoutEffect, useRef, useSignal } from '../lib/teact/teact'; import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom'; @@ -9,13 +9,14 @@ import useDerivedSignal from './useDerivedSignal'; import useDerivedState from './useDerivedState'; import useLastCallback from './useLastCallback'; import { useStateRef } from './useStateRef'; +import useSyncEffect from './useSyncEffect'; import useSyncEffectWithPrevDeps from './useSyncEffectWithPrevDeps'; const CLOSE_DURATION = 350; type BaseHookParams = { isOpen: boolean | undefined; - ref?: RefObject; + ref?: RefObject; noMountTransition?: boolean; noOpenTransition?: boolean; noCloseTransition?: boolean; @@ -34,7 +35,7 @@ type HookParamsWithShouldRender = BaseHookParams = { - ref: RefObject; + ref: RefObject; getIsClosing: Signal; }; @@ -67,13 +68,10 @@ export default function useShowTransition(null); - ref ||= localRef; + const ref = params.ref || localRef; const closingTimeoutRef = useRef(); const [getState, setState] = useSignal(); const optionsRef = useStateRef({ @@ -110,7 +108,7 @@ export default function useShowTransition { + const applyClassNames = useLastCallback(() => { const element = ref.current; if (!element) return; @@ -129,12 +127,25 @@ export default function useShowTransition { + ref.onChange = () => { + ref.onChange = undefined; + applyClassNames(); + }; + }, [applyClassNames, ref]); + + useLayoutEffect(applyClassNames, [applyClassNames, getState]); + + const withShouldRender = 'withShouldRender' in params && params.withShouldRender; const shouldRender = useDerivedState( () => (withShouldRender && getState() !== 'closed'), [withShouldRender, getState], ); + const getIsClosing = useDerivedSignal(() => getState() === 'closing', [getState]); if (withShouldRender) { diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index 5aaf78891..cf5a064a9 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -628,6 +628,7 @@ function setElementRef($element: VirtualElementTag, element: DOMElement | undefi if (typeof ref === 'object') { ref.current = element; + ref.onChange?.(); } else if (typeof ref === 'function') { ref(element); } diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index bc03afe53..df9fa3f0f 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -57,6 +57,11 @@ export interface VirtualElementFragment { export type StateHookSetter = (newValue: ((current: T) => T) | T) => void; +export interface RefObject { + current: T; + onChange?: NoneToVoidFunction; +} + export enum MountState { New, Mounted, @@ -99,9 +104,7 @@ interface ComponentInstance { }; refs?: { cursor: number; - byCursor: { - current: any; - }[]; + byCursor: RefObject[]; }; }; prepareForFrame?: () => void; @@ -547,6 +550,7 @@ function helpGc(componentInstance: ComponentInstance) { if (refs) { for (const hook of refs.byCursor) { hook.current = undefined as any; + hook.onChange = undefined as any; } } @@ -886,9 +890,9 @@ export function useCallback(newCallback: F, dependencies: return useMemo(() => newCallback, dependencies, debugKey); } -export function useRef(initial: T): { current: T }; -export function useRef(): { current: T | undefined }; // TT way (empty is `undefined`) -export function useRef(initial: null): { current: T | null }; // React way (empty is `null`) +export function useRef(initial: T): RefObject; +export function useRef(): RefObject; // TT way (empty is `undefined`) +export function useRef(initial: null): RefObject; // React way (empty is `null`) // eslint-disable-next-line no-null/no-null export function useRef(initial?: T | null) { if (!renderingInstance.hooks) {