Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/eui/changelogs/upcoming/9514.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed `EuiFlyoutManager` animation flickering when switching between flyout sessions by removing intermediate transition stages (backgrounding, returning, closing) and limiting opening animations to the initial flyout and first child only
158 changes: 146 additions & 12 deletions packages/eui/src/components/flyout/manager/activity_stage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,16 @@ describe('useFlyoutActivityStage', () => {
const TestComponent = ({
flyoutId,
level,
shouldAnimate,
}: {
flyoutId: string;
level: 'main' | 'child';
shouldAnimate?: boolean;
}) => {
const { activityStage, onAnimationEnd } = useFlyoutActivityStage({
flyoutId,
level,
shouldAnimate,
});

return (
Expand Down Expand Up @@ -175,7 +178,7 @@ describe('useFlyoutActivityStage', () => {
});

describe('stage transitions based on activity', () => {
it('transitions from ACTIVE to CLOSING when flyout becomes inactive', () => {
it('when shouldAnimate is false (default), transitions directly to final stage: ACTIVE to INACTIVE', () => {
let currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'main-1',
Expand All @@ -197,12 +200,10 @@ describe('useFlyoutActivityStage', () => {
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />
);

// Initially active
expect(screen.getByTestSubject('activity-stage')).toHaveTextContent(
STAGE_ACTIVE
);

// Change to inactive - session no longer contains main-1
currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'other-main',
Expand All @@ -217,12 +218,55 @@ describe('useFlyoutActivityStage', () => {
});
rerender(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_INACTIVE)
);
});

it('when shouldAnimate is true, transitions to intermediate CLOSING when flyout becomes inactive', () => {
let currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'main-1',
childFlyoutId: null,
flyouts: [
{
flyoutId: 'main-1',
level: LEVEL_MAIN,
activityStage: STAGE_ACTIVE,
},
],
});
mockUseFlyoutManager.mockImplementation(() => ({
state: currentMockState,
dispatch: mockDispatch,
}));

const { rerender } = render(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'other-main',
childFlyoutId: null,
flyouts: [
{
flyoutId: 'main-1',
level: LEVEL_MAIN,
activityStage: STAGE_ACTIVE,
},
],
});
rerender(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_CLOSING)
);
});

it('transitions from INACTIVE to RETURNING when flyout becomes active', () => {
it('when shouldAnimate is false (default), transitions directly: INACTIVE to ACTIVE', () => {
const stateWithInactive = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'other-main',
Expand All @@ -245,12 +289,10 @@ describe('useFlyoutActivityStage', () => {
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />
);

// Initially inactive
expect(screen.getByTestSubject('activity-stage')).toHaveTextContent(
STAGE_INACTIVE
);

// Change to active - session now contains main-1
currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'main-1',
Expand All @@ -265,14 +307,58 @@ describe('useFlyoutActivityStage', () => {
});
rerender(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_ACTIVE)
);
});

it('when shouldAnimate is true, transitions to intermediate RETURNING when flyout becomes active', () => {
const stateWithInactive = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'other-main',
childFlyoutId: null,
flyouts: [
{
flyoutId: 'main-1',
level: LEVEL_MAIN,
activityStage: STAGE_INACTIVE,
},
],
});
let currentMockState = stateWithInactive;
mockUseFlyoutManager.mockImplementation(() => ({
state: currentMockState,
dispatch: mockDispatch,
}));

const { rerender } = render(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

currentMockState = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'main-1',
childFlyoutId: null,
flyouts: [
{
flyoutId: 'main-1',
level: LEVEL_MAIN,
activityStage: STAGE_INACTIVE,
},
],
});
rerender(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_RETURNING)
);
});
});

describe('main flyout backgrounding logic', () => {
it('transitions to BACKGROUNDING when main flyout is active, has child, and layout is stacked', () => {
it('when shouldAnimate is false (default), transitions directly to BACKGROUNDED when main has child and layout is stacked', () => {
const stateWithChild = buildMockState({
layoutMode: LAYOUT_MODE_STACKED,
mainFlyoutId: 'main-1',
Expand All @@ -286,6 +372,27 @@ describe('useFlyoutActivityStage', () => {

render(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_BACKGROUNDED)
);
});

it('when shouldAnimate is true, transitions to BACKGROUNDING when main has child and layout is stacked', () => {
const stateWithChild = buildMockState({
layoutMode: LAYOUT_MODE_STACKED,
mainFlyoutId: 'main-1',
childFlyoutId: 'child-1',
flyouts: defaultFlyouts,
});
mockUseFlyoutManager.mockReturnValue({
state: stateWithChild,
dispatch: mockDispatch,
});

render(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_BACKGROUNDING)
);
Expand Down Expand Up @@ -350,7 +457,7 @@ describe('useFlyoutActivityStage', () => {
});

describe('main flyout returning logic', () => {
it('transitions from BACKGROUNDED to RETURNING when child is gone', () => {
it('when shouldAnimate is false (default), transitions directly from BACKGROUNDED to ACTIVE when child is gone', () => {
const stateWithBackgrounded = buildMockState({
layoutMode: LAYOUT_MODE_STACKED,
mainFlyoutId: 'main-1',
Expand All @@ -371,11 +478,11 @@ describe('useFlyoutActivityStage', () => {
render(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_RETURNING)
mockSetActivityStage('main-1', STAGE_ACTIVE)
);
});

it('transitions from BACKGROUNDING to RETURNING when child is gone', () => {
it('when shouldAnimate is false (default), transitions directly from BACKGROUNDING to ACTIVE when child is gone', () => {
const stateWithBackgrounding = buildMockState({
layoutMode: LAYOUT_MODE_STACKED,
mainFlyoutId: 'main-1',
Expand All @@ -395,12 +502,39 @@ describe('useFlyoutActivityStage', () => {

render(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_ACTIVE)
);
});

it('when shouldAnimate is true, transitions from BACKGROUNDED to RETURNING when child is gone', () => {
const stateWithBackgrounded = buildMockState({
layoutMode: LAYOUT_MODE_STACKED,
mainFlyoutId: 'main-1',
childFlyoutId: null,
flyouts: [
{
flyoutId: 'main-1',
level: LEVEL_MAIN,
activityStage: STAGE_BACKGROUNDED,
},
],
});
mockUseFlyoutManager.mockReturnValue({
state: stateWithBackgrounded,
dispatch: mockDispatch,
});

render(
<TestComponent flyoutId="main-1" level={LEVEL_MAIN} shouldAnimate />
);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_RETURNING)
);
});

it('transitions from BACKGROUNDED to RETURNING when layout changes to side-by-side', () => {
it('when shouldAnimate is false (default), transitions directly from BACKGROUNDED to ACTIVE when layout is side-by-side', () => {
const stateWithBackgrounded = buildMockState({
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
mainFlyoutId: 'main-1',
Expand All @@ -421,7 +555,7 @@ describe('useFlyoutActivityStage', () => {
render(<TestComponent flyoutId="main-1" level={LEVEL_MAIN} />);

expect(mockDispatch).toHaveBeenCalledWith(
mockSetActivityStage('main-1', STAGE_RETURNING)
mockSetActivityStage('main-1', STAGE_ACTIVE)
);
});

Expand Down
Loading