useShowTransitions: Workaround for noMountTransition not working after forced reflow
This commit is contained in:
parent
ff72279f1c
commit
3e48f3fbf9
@ -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 { useLayoutEffect, useRef, useSignal } from '../lib/teact/teact';
|
||||||
import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom';
|
import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom';
|
||||||
|
|
||||||
@ -9,13 +9,14 @@ import useDerivedSignal from './useDerivedSignal';
|
|||||||
import useDerivedState from './useDerivedState';
|
import useDerivedState from './useDerivedState';
|
||||||
import useLastCallback from './useLastCallback';
|
import useLastCallback from './useLastCallback';
|
||||||
import { useStateRef } from './useStateRef';
|
import { useStateRef } from './useStateRef';
|
||||||
|
import useSyncEffect from './useSyncEffect';
|
||||||
import useSyncEffectWithPrevDeps from './useSyncEffectWithPrevDeps';
|
import useSyncEffectWithPrevDeps from './useSyncEffectWithPrevDeps';
|
||||||
|
|
||||||
const CLOSE_DURATION = 350;
|
const CLOSE_DURATION = 350;
|
||||||
|
|
||||||
type BaseHookParams<RefType extends HTMLElement> = {
|
type BaseHookParams<RefType extends HTMLElement> = {
|
||||||
isOpen: boolean | undefined;
|
isOpen: boolean | undefined;
|
||||||
ref?: RefObject<RefType>;
|
ref?: RefObject<RefType | null>;
|
||||||
noMountTransition?: boolean;
|
noMountTransition?: boolean;
|
||||||
noOpenTransition?: boolean;
|
noOpenTransition?: boolean;
|
||||||
noCloseTransition?: boolean;
|
noCloseTransition?: boolean;
|
||||||
@ -34,7 +35,7 @@ type HookParamsWithShouldRender<RefType extends HTMLElement> = BaseHookParams<Re
|
|||||||
};
|
};
|
||||||
|
|
||||||
type HookResult<RefType extends HTMLElement> = {
|
type HookResult<RefType extends HTMLElement> = {
|
||||||
ref: RefObject<RefType>;
|
ref: RefObject<RefType | null>;
|
||||||
getIsClosing: Signal<boolean>;
|
getIsClosing: Signal<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,13 +68,10 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
|
|||||||
prefix = '',
|
prefix = '',
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
} = params;
|
} = params;
|
||||||
let ref = params.ref;
|
|
||||||
|
|
||||||
const withShouldRender = 'withShouldRender' in params && params.withShouldRender;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const localRef = useRef<RefType>(null);
|
const localRef = useRef<RefType>(null);
|
||||||
ref ||= localRef;
|
const ref = params.ref || localRef;
|
||||||
const closingTimeoutRef = useRef<number>();
|
const closingTimeoutRef = useRef<number>();
|
||||||
const [getState, setState] = useSignal<State | undefined>();
|
const [getState, setState] = useSignal<State | undefined>();
|
||||||
const optionsRef = useStateRef({
|
const optionsRef = useStateRef({
|
||||||
@ -110,7 +108,7 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
const applyClassNames = useLastCallback(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
@ -129,12 +127,25 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
|
|||||||
toggleExtraClass(element, `${prefix}open`, hasOpenClass);
|
toggleExtraClass(element, `${prefix}open`, hasOpenClass);
|
||||||
toggleExtraClass(element, `${prefix}not-open`, !hasOpenClass);
|
toggleExtraClass(element, `${prefix}not-open`, !hasOpenClass);
|
||||||
toggleExtraClass(element, `${prefix}closing`, isClosing);
|
toggleExtraClass(element, `${prefix}closing`, isClosing);
|
||||||
}, [className, getState, prefix, ref]);
|
});
|
||||||
|
|
||||||
|
// Workaround for Chrome causing forced reflow in the middle of mutation phase when unmounting a focused element.
|
||||||
|
// Due to such forced reflow setting initial class names in the first layout effect causes transitions to start.
|
||||||
|
useSyncEffect(() => {
|
||||||
|
ref.onChange = () => {
|
||||||
|
ref.onChange = undefined;
|
||||||
|
applyClassNames();
|
||||||
|
};
|
||||||
|
}, [applyClassNames, ref]);
|
||||||
|
|
||||||
|
useLayoutEffect(applyClassNames, [applyClassNames, getState]);
|
||||||
|
|
||||||
|
const withShouldRender = 'withShouldRender' in params && params.withShouldRender;
|
||||||
const shouldRender = useDerivedState(
|
const shouldRender = useDerivedState(
|
||||||
() => (withShouldRender && getState() !== 'closed'),
|
() => (withShouldRender && getState() !== 'closed'),
|
||||||
[withShouldRender, getState],
|
[withShouldRender, getState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getIsClosing = useDerivedSignal(() => getState() === 'closing', [getState]);
|
const getIsClosing = useDerivedSignal(() => getState() === 'closing', [getState]);
|
||||||
|
|
||||||
if (withShouldRender) {
|
if (withShouldRender) {
|
||||||
|
|||||||
@ -628,6 +628,7 @@ function setElementRef($element: VirtualElementTag, element: DOMElement | undefi
|
|||||||
|
|
||||||
if (typeof ref === 'object') {
|
if (typeof ref === 'object') {
|
||||||
ref.current = element;
|
ref.current = element;
|
||||||
|
ref.onChange?.();
|
||||||
} else if (typeof ref === 'function') {
|
} else if (typeof ref === 'function') {
|
||||||
ref(element);
|
ref(element);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,11 @@ export interface VirtualElementFragment {
|
|||||||
|
|
||||||
export type StateHookSetter<T> = (newValue: ((current: T) => T) | T) => void;
|
export type StateHookSetter<T> = (newValue: ((current: T) => T) | T) => void;
|
||||||
|
|
||||||
|
export interface RefObject<T = any> {
|
||||||
|
current: T;
|
||||||
|
onChange?: NoneToVoidFunction;
|
||||||
|
}
|
||||||
|
|
||||||
export enum MountState {
|
export enum MountState {
|
||||||
New,
|
New,
|
||||||
Mounted,
|
Mounted,
|
||||||
@ -99,9 +104,7 @@ interface ComponentInstance {
|
|||||||
};
|
};
|
||||||
refs?: {
|
refs?: {
|
||||||
cursor: number;
|
cursor: number;
|
||||||
byCursor: {
|
byCursor: RefObject[];
|
||||||
current: any;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
prepareForFrame?: () => void;
|
prepareForFrame?: () => void;
|
||||||
@ -547,6 +550,7 @@ function helpGc(componentInstance: ComponentInstance) {
|
|||||||
if (refs) {
|
if (refs) {
|
||||||
for (const hook of refs.byCursor) {
|
for (const hook of refs.byCursor) {
|
||||||
hook.current = undefined as any;
|
hook.current = undefined as any;
|
||||||
|
hook.onChange = undefined as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,9 +890,9 @@ export function useCallback<F extends AnyFunction>(newCallback: F, dependencies:
|
|||||||
return useMemo(() => newCallback, dependencies, debugKey);
|
return useMemo(() => newCallback, dependencies, debugKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRef<T>(initial: T): { current: T };
|
export function useRef<T>(initial: T): RefObject<T>;
|
||||||
export function useRef<T>(): { current: T | undefined }; // TT way (empty is `undefined`)
|
export function useRef<T>(): RefObject<T | undefined>; // TT way (empty is `undefined`)
|
||||||
export function useRef<T>(initial: null): { current: T | null }; // React way (empty is `null`)
|
export function useRef<T>(initial: null): RefObject<T | null>; // React way (empty is `null`)
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
export function useRef<T>(initial?: T | null) {
|
export function useRef<T>(initial?: T | null) {
|
||||||
if (!renderingInstance.hooks) {
|
if (!renderingInstance.hooks) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user