Skip to content
3 changes: 3 additions & 0 deletions packages/eui/changelogs/upcoming/9413.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Added `historyKey` prop (type `symbol`) to `EuiFlyout` and the flyout manager API to support scoped flyout history.
- Only flyouts sharing the same `Symbol` reference share Back button navigation and history entries; omitting `historyKey` gives each session its own isolated history group.
- `ACTION_CLOSE_ALL` now closes only the current history group rather than all open flyouts.
21 changes: 21 additions & 0 deletions packages/eui/src/components/flyout/flyout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,27 @@ describe('EuiFlyout', () => {
const childFlyout = getByTestSubject('child-flyout');
expect(childFlyout).not.toHaveAttribute('data-managed-flyout-level');
});

it('accepts historyKey prop with session="start" and renders without error', () => {
const sharedKey = Symbol();
const { getByRole } = render(
<EuiFlyoutManager>
<EuiFlyout
session="start"
historyKey={sharedKey}
onClose={() => {}}
flyoutMenuProps={{ title: 'Main Flyout' }}
aria-label="Test flyout"
>
Content
</EuiFlyout>
</EuiFlyoutManager>
);
const dialog = getByRole('dialog');
expect(dialog).toBeInTheDocument();
expect(dialog).toHaveAttribute('aria-label', 'Test flyout');
expect(dialog).toHaveAttribute('data-managed-flyout-level', 'main');
});
});

describe('ref forwarding', () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/eui/src/components/flyout/flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export type EuiFlyoutProps<T extends ElementType = 'div' | 'nav'> = Omit<
| typeof SESSION_START
| typeof SESSION_INHERIT
| typeof SESSION_NEVER;
/**
* Optional Symbol to scope flyout history. Only flyouts that receive the same Symbol reference share Back button and history; omit to get a unique group per session.
* @default undefined (each session gets a unique key and does not share history)
*/
historyKey?: symbol;
/**
* Callback fired when the flyout becomes active/visible, which may happen programmatically from history navigation.
*/
Expand All @@ -68,7 +73,7 @@ export const EuiFlyout = forwardRef<
HTMLDivElement | HTMLElement,
EuiFlyoutProps<'div' | 'nav'>
>((props, ref) => {
const { as, onClose, onActive, session, ...rest } =
const { as, onClose, onActive, session, historyKey, ...rest } =
usePropsWithComponentDefaults('EuiFlyout', props);
const hasActiveSession = useHasActiveSession();
const isInsideParentFlyout = useIsInsideParentFlyout();
Expand Down Expand Up @@ -101,6 +106,7 @@ export const EuiFlyout = forwardRef<
return (
<EuiFlyoutMain
{...rest}
historyKey={historyKey}
onClose={onClose}
onActive={onActive}
as="div"
Expand All @@ -114,6 +120,7 @@ export const EuiFlyout = forwardRef<
return (
<EuiFlyoutChild
{...rest}
historyKey={historyKey}
onClose={onClose}
onActive={onActive}
as="div"
Expand Down
11 changes: 10 additions & 1 deletion packages/eui/src/components/flyout/manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
[Documentation - sources](../../../../../website/docs/components/containers/flyout/session-management.mdx)

Session management for EuiFlyout is enabled with the `session` prop. You can read more about that
in the [main EuiFlyout developer README](../README.md).
in the [main EuiFlyout developer README](../README.md).

## History scoping (`historyKey`)

Flyout history (Back button and history popover) is scoped by an optional **`historyKey`** prop (type `symbol`).

- **When `historyKey` is omitted**: Each session gets a unique key internally (`Symbol()`), so flyouts do not share history. The Back button will not appear for cross-session navigation.
- **When `historyKey` is set**: Only flyouts that receive the **same Symbol reference** share history. Use a shared Symbol (e.g. `const key = Symbol();` passed to multiple `EuiFlyout` instances) to group related flyouts so their Back button and history only show entries from that group.

This allows different domains (e.g. different product areas) to use `session="start"` without mixing their histories. Child flyouts inherit the main flyout's key and do not pass their own.

## Components and hooks

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const createTestSession = (
title,
childFlyoutId,
childHistory: [],
historyKey: Symbol(mainFlyoutId),
zIndex: 0,
...overrides,
});
Expand Down
26 changes: 26 additions & 0 deletions packages/eui/src/components/flyout/manager/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ describe('flyout manager actions', () => {
'main',
LEVEL_MAIN,
'm',
undefined,
'faceHappy'
);

Expand All @@ -127,6 +128,7 @@ describe('flyout manager actions', () => {
title: 'main',
level: LEVEL_MAIN,
size: 'm',
historyKey: undefined,
iconType: 'faceHappy',
});
});
Expand All @@ -137,6 +139,7 @@ describe('flyout manager actions', () => {
'main',
LEVEL_MAIN,
'm',
undefined,
'faceHappy',
100
);
Expand All @@ -147,10 +150,33 @@ describe('flyout manager actions', () => {
title: 'main',
level: LEVEL_MAIN,
size: 'm',
historyKey: undefined,
iconType: 'faceHappy',
minWidth: 100,
});
});

it('should include historyKey in action when provided', () => {
const key = Symbol('test');
const action = addFlyout(
'flyout-1',
'main',
LEVEL_MAIN,
'm',
key,
'faceHappy'
);

expect(action).toEqual({
type: ACTION_ADD,
flyoutId: 'flyout-1',
title: 'main',
level: LEVEL_MAIN,
size: 'm',
historyKey: key,
iconType: 'faceHappy',
});
});
});

describe('closeFlyout', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/eui/src/components/flyout/manager/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface AddFlyoutAction extends BaseAction {
title: string;
level: EuiFlyoutLevel;
size?: string;
historyKey?: symbol;
iconType?: IconType;
minWidth?: number;
}
Expand Down Expand Up @@ -166,13 +167,15 @@ export type Action =
* - `title` is used for the flyout menu.
* - `level` determines whether the flyout is `main` or `child`.
* - Optional `size` is the named EUI size (e.g. `s`, `m`, `l`).
* - Optional `historyKey` (Symbol) scopes history; only flyouts with the same reference share Back/history. Omit for a unique group per session.
* - Optional `iconType` is shown next to the session title in the history menu.
*/
export const addFlyout = (
flyoutId: string,
title: string,
level: EuiFlyoutLevel = LEVEL_MAIN,
size?: string,
historyKey?: symbol,
iconType?: IconType,
minWidth?: number
): AddFlyoutAction => ({
Expand All @@ -181,6 +184,7 @@ export const addFlyout = (
title,
level,
size,
historyKey,
iconType,
minWidth,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const buildMockState = ({
}>,
} = {}) => ({
layoutMode,
sessions: [{ mainFlyoutId, childFlyoutId }],
sessions: [{ mainFlyoutId, childFlyoutId, historyKey: Symbol() }],
flyouts,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
*/
export interface EuiManagedFlyoutProps extends EuiFlyoutComponentProps {
level: EuiFlyoutLevel;
historyKey?: symbol;
flyoutMenuProps?: Omit<EuiFlyoutMenuProps, 'historyItems' | 'showBackButton'>;
onActive?: () => void;
}
Expand Down Expand Up @@ -82,6 +83,7 @@ export const EuiManagedFlyout = forwardRef<HTMLElement, EuiManagedFlyoutProps>(
level,
size: sizeProp,
minWidth,
historyKey,
css: customCss,
flyoutMenuProps: _flyoutMenuProps,
...props
Expand Down Expand Up @@ -212,6 +214,7 @@ export const EuiManagedFlyout = forwardRef<HTMLElement, EuiManagedFlyoutProps>(
title!,
level,
size as string,
level === LEVEL_MAIN ? historyKey : undefined,
_flyoutMenuProps?.iconType,
typeof minWidth === 'number' ? minWidth : undefined
);
Expand All @@ -237,6 +240,7 @@ export const EuiManagedFlyout = forwardRef<HTMLElement, EuiManagedFlyoutProps>(
level,
size,
minWidth,
historyKey,
_flyoutMenuProps?.iconType,
addFlyout,
closeFlyout,
Expand Down
Loading
Loading