mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(skin): improve buffering, overlays, and input feedback (#1547)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,6 +29,28 @@ describe('createTransition', () => {
|
||||
await promise;
|
||||
expect(handler.state.current).toEqual({ active: true, status: 'idle' });
|
||||
});
|
||||
|
||||
it('cancels active animations and flushes styles when restarting an active transition', async () => {
|
||||
const handler = createTransition();
|
||||
const el = document.createElement('div');
|
||||
const getOffsetHeight = vi.spyOn(el, 'offsetHeight', 'get');
|
||||
const cancel = vi.fn();
|
||||
Object.defineProperty(el, 'getAnimations', {
|
||||
value: vi.fn(() => [{ cancel }]),
|
||||
});
|
||||
|
||||
await handler.open();
|
||||
await vi.waitFor(() => {
|
||||
expect(handler.state.current.status).toBe('idle');
|
||||
});
|
||||
|
||||
handler.open(el);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(cancel).toHaveBeenCalled();
|
||||
});
|
||||
expect(getOffsetHeight).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('close', () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { TransitionState } from '../../core/ui/transition';
|
||||
|
||||
export interface TransitionApi {
|
||||
state: State<TransitionState>;
|
||||
open(): Promise<void>;
|
||||
open(el?: HTMLElement | null): Promise<void>;
|
||||
close(el: HTMLElement | null): Promise<void>;
|
||||
cancel(): void;
|
||||
destroy(): void;
|
||||
@@ -15,7 +15,8 @@ export interface TransitionApi {
|
||||
*
|
||||
* **Open:** patches `{ active: true, status: 'starting' }`, then after a
|
||||
* double-RAF patches `{ status: 'idle' }` so the browser paints the
|
||||
* initial ("from") state before transitioning.
|
||||
* initial ("from") state before transitioning. Reopening an active transition
|
||||
* flushes styles first so CSS transitions can restart.
|
||||
*
|
||||
* **Close:** patches `{ status: 'ending' }` (keeping `active: true` so the
|
||||
* element stays mounted), then after a double-RAF waits for
|
||||
@@ -28,17 +29,27 @@ export function createTransition(): TransitionApi {
|
||||
let rafId1 = 0;
|
||||
let rafId2 = 0;
|
||||
|
||||
function open(): Promise<void> {
|
||||
function open(el: HTMLElement | null = null): Promise<void> {
|
||||
cancelAnimationFrame(rafId1);
|
||||
cancelAnimationFrame(rafId2);
|
||||
rafId1 = 0;
|
||||
rafId2 = 0;
|
||||
|
||||
const restarting = state.current.active;
|
||||
|
||||
if (restarting) {
|
||||
state.patch({ status: 'idle' });
|
||||
}
|
||||
|
||||
state.patch({ active: true, status: 'starting' });
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
rafId1 = requestAnimationFrame(() => {
|
||||
rafId1 = 0;
|
||||
if (restarting) {
|
||||
cancelAnimations(el);
|
||||
flushStyles(el);
|
||||
}
|
||||
rafId2 = requestAnimationFrame(() => {
|
||||
rafId2 = 0;
|
||||
if (destroyed || !state.current.active) return resolve();
|
||||
@@ -96,6 +107,19 @@ export function createTransition(): TransitionApi {
|
||||
};
|
||||
}
|
||||
|
||||
function flushStyles(el: HTMLElement | null): void {
|
||||
if (!el) return;
|
||||
void el.offsetHeight;
|
||||
}
|
||||
|
||||
function cancelAnimations(el: HTMLElement | null): void {
|
||||
const animations = el?.getAnimations?.({ subtree: true }) ?? [];
|
||||
|
||||
for (const animation of animations) {
|
||||
animation.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
function waitForAnimations(el: HTMLElement | null): Promise<void> {
|
||||
if (!el) return Promise.resolve();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user