Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Loading