Skip to content

Commit 4b95cb8

Browse files
committed
Use flyout manager state for push offsets
1 parent 348500d commit 4b95cb8

File tree

10 files changed

+103
-39
lines changed

10 files changed

+103
-39
lines changed

packages/eui/src/components/flyout/flyout.component.tsx

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {
4242
useFlyoutId,
4343
useFlyoutWidth,
4444
useIsFlyoutActive,
45+
useFlyoutManager,
46+
useHasPushPadding,
4547
} from './manager';
4648

4749
import { CommonProps, PropsOfElement } from '../common';
@@ -51,7 +53,6 @@ import type { EuiButtonIconPropsForButton } from '../button';
5153
import { EuiI18n } from '../i18n';
5254
import { useResizeObserver } from '../observer/resize_observer';
5355
import { EuiScreenReaderOnly } from '../accessibility';
54-
import { useMutationObserver } from '../observer/mutation_observer';
5556

5657
import { EuiFlyoutCloseButton } from './_flyout_close_button';
5758
import { euiFlyoutStyles, composeFlyoutInlineStyles } from './flyout.styles';
@@ -276,6 +277,13 @@ export const EuiFlyoutComponent = forwardRef(
276277
const flyoutId = useFlyoutId(id);
277278
const layoutMode = useFlyoutLayoutMode();
278279
const isActiveManagedFlyout = useIsFlyoutActive(flyoutId);
280+
const flyoutManager = useFlyoutManager();
281+
282+
// Use a ref to access the latest flyoutManager without triggering effect re-runs
283+
const flyoutManagerRef = useRef(flyoutManager);
284+
useEffect(() => {
285+
flyoutManagerRef.current = flyoutManager;
286+
}, [flyoutManager]);
279287

280288
const {
281289
onMouseDown: onMouseDownResizableButton,
@@ -325,18 +333,27 @@ export const EuiFlyoutComponent = forwardRef(
325333
const cssVarName = `--euiPushFlyoutOffset${
326334
side === 'left' ? 'InlineStart' : 'InlineEnd'
327335
}`;
336+
const managerSide = side === 'left' ? 'left' : 'right';
328337

329338
if (shouldApplyPadding) {
330339
document.body.style[paddingSide] = `${width}px`;
331340
setGlobalCSSVariables({
332341
[cssVarName]: `${width}px`,
333342
});
343+
// Update manager state if in managed context
344+
if (isInManagedContext && flyoutManagerRef.current) {
345+
flyoutManagerRef.current.setPushPadding(managerSide, width);
346+
}
334347
} else {
335348
// Explicitly remove padding when this push flyout becomes inactive
336349
document.body.style[paddingSide] = '';
337350
setGlobalCSSVariables({
338351
[cssVarName]: null,
339352
});
353+
// Clear manager state if in managed context
354+
if (isInManagedContext && flyoutManagerRef.current) {
355+
flyoutManagerRef.current.setPushPadding(managerSide, 0);
356+
}
340357
}
341358

342359
// Cleanup on unmount
@@ -345,6 +362,10 @@ export const EuiFlyoutComponent = forwardRef(
345362
setGlobalCSSVariables({
346363
[cssVarName]: null,
347364
});
365+
// Clear manager state on unmount if in managed context
366+
if (isInManagedContext && flyoutManagerRef.current) {
367+
flyoutManagerRef.current.setPushPadding(managerSide, 0);
368+
}
348369
};
349370
}, [
350371
isPushed,
@@ -632,44 +653,13 @@ export const EuiFlyoutComponent = forwardRef(
632653
const maskCombinedRefs = useCombinedRefs([maskProps?.maskRef, maskRef]);
633654

634655
/**
635-
* Track whether body padding from a push flyout exists to coordinate scroll locking.
636-
* Use state to allow updates when padding changes.
637-
*/
638-
const [hasPushFlyoutPadding, setHasPushFlyoutPadding] = useState(() => {
639-
if (isPushed || !isInManagedContext) return false;
640-
const leftPadding = document.body.style.paddingInlineStart;
641-
const rightPadding = document.body.style.paddingInlineEnd;
642-
return !!(leftPadding || rightPadding);
643-
});
644-
645-
/**
646-
* Monitor body padding and update state when it changes.
647-
* This allows overlay flyouts to enable scroll lock once push padding is removed.
656+
* For overlay flyouts in managed contexts, coordinate scroll locking with push flyout state.
657+
* Only enable scroll lock when there's no active push padding in the manager.
648658
*/
649-
const checkPadding = useCallback(() => {
650-
const leftPadding = document.body.style.paddingInlineStart;
651-
const rightPadding = document.body.style.paddingInlineEnd;
652-
const hasPadding = !!(leftPadding || rightPadding);
653-
setHasPushFlyoutPadding(hasPadding);
654-
}, []);
655-
656-
// Monitor body style changes for overlay flyouts in managed contexts
657-
useMutationObserver(
658-
isPushed || !isInManagedContext ? null : document.body,
659-
checkPadding,
660-
{ attributeFilter: ['style'] }
661-
);
662-
663-
// Check padding state immediately after render
664-
useLayoutEffect(() => {
665-
if (!isPushed && isInManagedContext) {
666-
checkPadding();
667-
} else {
668-
setHasPushFlyoutPadding(false);
669-
}
670-
}, [isPushed, isInManagedContext, checkPadding]);
671-
672-
const shouldUseScrollLock = hasOverlayMask && !hasPushFlyoutPadding;
659+
const hasPushPaddingInManager = useHasPushPadding();
660+
const shouldDeferScrollLock =
661+
!isPushed && isInManagedContext && hasPushPaddingInManager;
662+
const shouldUseScrollLock = hasOverlayMask && !shouldDeferScrollLock;
673663

674664
return (
675665
<EuiFlyoutOverlay

packages/eui/src/components/flyout/manager/actions.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export const ACTION_SET_ACTIVITY_STAGE = `${PREFIX}/setActivityStage` as const;
3535
export const ACTION_GO_BACK = `${PREFIX}/goBack` as const;
3636
/** Dispatched to navigate to a specific flyout (remove all sessions after it). */
3737
export const ACTION_GO_TO_FLYOUT = `${PREFIX}/goToFlyout` as const;
38+
/** Dispatched to set push padding offset for a side. */
39+
export const ACTION_SET_PUSH_PADDING = `${PREFIX}/setPushPadding` as const;
3840

3941
/**
4042
* Add a flyout to manager state. The manager will create or update
@@ -91,6 +93,13 @@ export interface GoToFlyoutAction extends BaseAction {
9193
flyoutId: string;
9294
}
9395

96+
/** Set push padding offset for a specific side. */
97+
export interface SetPushPaddingAction extends BaseAction {
98+
type: typeof ACTION_SET_PUSH_PADDING;
99+
side: 'left' | 'right';
100+
width: number;
101+
}
102+
94103
/** Union of all flyout manager actions. */
95104
export type Action =
96105
| AddFlyoutAction
@@ -100,7 +109,8 @@ export type Action =
100109
| SetLayoutModeAction
101110
| SetActivityStageAction
102111
| GoBackAction
103-
| GoToFlyoutAction;
112+
| GoToFlyoutAction
113+
| SetPushPaddingAction;
104114

105115
/**
106116
* Register a flyout with the manager.
@@ -173,3 +183,13 @@ export const goToFlyout = (flyoutId: string): GoToFlyoutAction => ({
173183
type: ACTION_GO_TO_FLYOUT,
174184
flyoutId,
175185
});
186+
187+
/** Set push padding offset for a specific side. */
188+
export const setPushPadding = (
189+
side: 'left' | 'right',
190+
width: number
191+
): SetPushPaddingAction => ({
192+
type: ACTION_SET_PUSH_PADDING,
193+
side,
194+
width,
195+
});

packages/eui/src/components/flyout/manager/hooks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export {
2525
useFlyoutWidth,
2626
useParentFlyoutSize,
2727
useHasChildFlyout,
28+
usePushPaddingOffsets,
29+
useHasPushPadding,
2830
} from './selectors';
2931

3032
export { useFlyoutLayoutMode } from './layout_mode';

packages/eui/src/components/flyout/manager/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export {
1414
closeFlyout as closeFlyoutAction,
1515
setActiveFlyout as setActiveFlyoutAction,
1616
setFlyoutWidth as setFlyoutWidthAction,
17+
setPushPadding as setPushPaddingAction,
1718
setActivityStage as setActivityStageAction,
1819
} from './actions';
1920

@@ -42,6 +43,8 @@ export {
4243
useIsInManagedFlyout,
4344
useHasActiveSession,
4445
useParentFlyoutSize,
46+
usePushPaddingOffsets,
47+
useHasPushPadding,
4548
} from './hooks';
4649

4750
export { EuiFlyoutChild, type EuiFlyoutChildProps } from './flyout_child';

packages/eui/src/components/flyout/manager/reducer.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('flyoutManagerReducer', () => {
4949
sessions: [],
5050
flyouts: [],
5151
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
52+
pushPadding: { left: 0, right: 0 },
5253
});
5354
});
5455
});

packages/eui/src/components/flyout/manager/reducer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ACTION_SET_ACTIVITY_STAGE,
1616
ACTION_GO_BACK,
1717
ACTION_GO_TO_FLYOUT,
18+
ACTION_SET_PUSH_PADDING,
1819
Action,
1920
} from './actions';
2021
import { LAYOUT_MODE_SIDE_BY_SIDE, LEVEL_MAIN, STAGE_OPENING } from './const';
@@ -31,6 +32,7 @@ export const initialState: EuiFlyoutManagerState = {
3132
sessions: [],
3233
flyouts: [],
3334
layoutMode: LAYOUT_MODE_SIDE_BY_SIDE,
35+
pushPadding: { left: 0, right: 0 },
3436
};
3537

3638
/**
@@ -257,6 +259,18 @@ export function flyoutManagerReducer(
257259
return { ...state, sessions: newSessions, flyouts: newFlyouts };
258260
}
259261

262+
// Set push padding offset for a specific side
263+
case ACTION_SET_PUSH_PADDING: {
264+
const { side, width } = action;
265+
return {
266+
...state,
267+
pushPadding: {
268+
...(state.pushPadding ?? { left: 0, right: 0 }),
269+
[side]: width,
270+
},
271+
};
272+
}
273+
260274
default:
261275
return state;
262276
}

packages/eui/src/components/flyout/manager/selectors.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('Flyout Manager Selectors', () => {
4545
closeFlyout: jest.fn(),
4646
setActiveFlyout: jest.fn(),
4747
setFlyoutWidth: jest.fn(),
48+
setPushPadding: jest.fn(),
4849
goBack: jest.fn(),
4950
goToFlyout: jest.fn(),
5051
historyItems: [],
@@ -77,6 +78,7 @@ describe('Flyout Manager Selectors', () => {
7778
closeFlyout: jest.fn(),
7879
setActiveFlyout: jest.fn(),
7980
setFlyoutWidth: jest.fn(),
81+
setPushPadding: jest.fn(),
8082
goBack: jest.fn(),
8183
goToFlyout: jest.fn(),
8284
historyItems: [],
@@ -101,6 +103,7 @@ describe('Flyout Manager Selectors', () => {
101103
closeFlyout: jest.fn(),
102104
setActiveFlyout: jest.fn(),
103105
setFlyoutWidth: jest.fn(),
106+
setPushPadding: jest.fn(),
104107
goBack: jest.fn(),
105108
goToFlyout: jest.fn(),
106109
historyItems: [],
@@ -132,6 +135,7 @@ describe('Flyout Manager Selectors', () => {
132135
closeFlyout: jest.fn(),
133136
setActiveFlyout: jest.fn(),
134137
setFlyoutWidth: jest.fn(),
138+
setPushPadding: jest.fn(),
135139
goBack: jest.fn(),
136140
goToFlyout: jest.fn(),
137141
historyItems: [],
@@ -173,6 +177,7 @@ describe('Flyout Manager Selectors', () => {
173177
closeFlyout: jest.fn(),
174178
setActiveFlyout: jest.fn(),
175179
setFlyoutWidth: jest.fn(),
180+
setPushPadding: jest.fn(),
176181
goBack: jest.fn(),
177182
goToFlyout: jest.fn(),
178183
historyItems: [],

packages/eui/src/components/flyout/manager/selectors.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,18 @@ export const useHasChildFlyout = (flyoutId: string) => {
8787
const session = useSession(flyoutId);
8888
return !!session?.childFlyoutId;
8989
};
90+
91+
/** Get the current push padding offsets from manager state. */
92+
export const usePushPaddingOffsets = () => {
93+
const context = useFlyoutManager();
94+
if (!context) {
95+
return { left: 0, right: 0 };
96+
}
97+
return context.state.pushPadding ?? { left: 0, right: 0 };
98+
};
99+
100+
/** True if there's any active push padding (left or right side). */
101+
export const useHasPushPadding = () => {
102+
const pushPadding = usePushPaddingOffsets();
103+
return pushPadding.left > 0 || pushPadding.right > 0;
104+
};

packages/eui/src/components/flyout/manager/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
closeFlyout as closeFlyoutAction,
1414
setActiveFlyout as setActiveFlyoutAction,
1515
setFlyoutWidth as setFlyoutWidthAction,
16+
setPushPadding as setPushPaddingAction,
1617
goBack as goBackAction,
1718
goToFlyout as goToFlyoutAction,
1819
} from './actions';
@@ -34,6 +35,7 @@ export interface FlyoutManagerStore {
3435
closeFlyout: (flyoutId: string) => void;
3536
setActiveFlyout: (flyoutId: string | null) => void;
3637
setFlyoutWidth: (flyoutId: string, width: number) => void;
38+
setPushPadding: (side: 'left' | 'right', width: number) => void;
3739
goBack: () => void;
3840
goToFlyout: (flyoutId: string) => void;
3941
historyItems: Array<{
@@ -106,6 +108,8 @@ function createStore(
106108
setActiveFlyout: (flyoutId) => dispatch(setActiveFlyoutAction(flyoutId)),
107109
setFlyoutWidth: (flyoutId, width) =>
108110
dispatch(setFlyoutWidthAction(flyoutId, width)),
111+
setPushPadding: (side, width) =>
112+
dispatch(setPushPaddingAction(side, width)),
109113
goBack: () => dispatch(goBackAction()),
110114
goToFlyout: (flyoutId) => dispatch(goToFlyoutAction(flyoutId)),
111115
historyItems: computeHistoryItems(), // Initialize with current state

packages/eui/src/components/flyout/manager/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,19 @@ export interface FlyoutSession {
5656
title: string;
5757
}
5858

59+
export interface PushPaddingOffsets {
60+
/** Push padding applied to the left side (in pixels) */
61+
left: number;
62+
/** Push padding applied to the right side (in pixels) */
63+
right: number;
64+
}
65+
5966
export interface EuiFlyoutManagerState {
6067
sessions: FlyoutSession[];
6168
flyouts: EuiManagedFlyoutState[];
6269
layoutMode: EuiFlyoutLayoutMode;
70+
/** Active push padding offsets (updated by active push flyouts) */
71+
pushPadding?: PushPaddingOffsets;
6372
}
6473

6574
/**
@@ -78,6 +87,7 @@ export interface FlyoutManagerApi {
7887
closeFlyout: (flyoutId: string) => void;
7988
setActiveFlyout: (flyoutId: string | null) => void;
8089
setFlyoutWidth: (flyoutId: string, width: number) => void;
90+
setPushPadding: (side: 'left' | 'right', width: number) => void;
8191
goBack: () => void;
8292
goToFlyout: (flyoutId: string) => void;
8393
historyItems: Array<{

0 commit comments

Comments
 (0)