diff --git a/pages/app-layout/utils/external-global-left-panel-widget.tsx b/pages/app-layout/utils/external-global-left-panel-widget.tsx index 5283d3362f..33ca05802b 100644 --- a/pages/app-layout/utils/external-global-left-panel-widget.tsx +++ b/pages/app-layout/utils/external-global-left-panel-widget.tsx @@ -139,4 +139,8 @@ registerLeftDrawer({ onHeaderActionClick: ({ detail }) => { console.log('onHeaderActionClick: ', detail); }, + + onToggleFocusMode: ({ detail }) => { + console.log('onToggleFocusMode: ', detail); + }, }); diff --git a/src/app-layout/__tests__/runtime-drawers-widgetized.test.tsx b/src/app-layout/__tests__/runtime-drawers-widgetized.test.tsx index bbf9714607..7f80f87403 100644 --- a/src/app-layout/__tests__/runtime-drawers-widgetized.test.tsx +++ b/src/app-layout/__tests__/runtime-drawers-widgetized.test.tsx @@ -257,6 +257,31 @@ describeEachAppLayout({ themes: ['refresh-toolbar'] }, ({ size }) => { } ); + test(`calls onToggleFocusMode handler by entering / exiting focus mode in left runtime drawer)`, () => { + const drawerId = 'global-drawer'; + const onToggleFocusMode = jest.fn(); + awsuiWidgetPlugins.registerLeftDrawer({ + ...drawerDefaults, + id: drawerId, + isExpandable: true, + onToggleFocusMode: event => onToggleFocusMode(event.detail), + }); + const renderProps = renderComponent(); + const { globalDrawersWrapper } = renderProps; + + globalDrawersWrapper.findAiDrawerTrigger()!.click(); + if (size === 'mobile') { + expect(globalDrawersWrapper.findExpandedModeButtonByActiveDrawerId(drawerId)).toBeFalsy(); + } else { + createWrapper().findButtonGroup()!.findButtonById('expand')!.click(); + expect(globalDrawersWrapper.findDrawerById(drawerId)!.isDrawerInExpandedMode()).toBe(true); + expect(onToggleFocusMode).toHaveBeenCalledWith({ isExpanded: true }); + createWrapper().findButtonGroup()!.findButtonById('expand')!.click(); + expect(globalDrawersWrapper.isLayoutInDrawerExpandedMode()).toBe(false); + expect(onToggleFocusMode).toHaveBeenCalledWith({ isExpanded: false }); + } + }); + describe('metrics', () => { let sendPanoramaMetricSpy: jest.SpyInstance; beforeEach(() => { diff --git a/src/app-layout/runtime-drawer/index.tsx b/src/app-layout/runtime-drawer/index.tsx index deb1eb29c7..63d932c506 100644 --- a/src/app-layout/runtime-drawer/index.tsx +++ b/src/app-layout/runtime-drawer/index.tsx @@ -149,6 +149,7 @@ export const mapRuntimeConfigToAiDrawer = ( onToggle?: NonCancelableEventHandler; headerActions?: ReadonlyArray; exitExpandedModeTrigger?: React.ReactNode; + onToggleFocusMode?: NonCancelableEventHandler<{ isExpanded: boolean }>; } => { const { mountContent, unmountContent, trigger, exitExpandedModeTrigger, ...runtimeDrawer } = runtimeConfig; diff --git a/src/app-layout/visual-refresh-toolbar/interfaces.ts b/src/app-layout/visual-refresh-toolbar/interfaces.ts index d5ed39095a..5d91283079 100644 --- a/src/app-layout/visual-refresh-toolbar/interfaces.ts +++ b/src/app-layout/visual-refresh-toolbar/interfaces.ts @@ -27,6 +27,7 @@ export type InternalDrawer = AppLayoutProps.Drawer & { headerActions?: ReadonlyArray; onHeaderActionClick?: NonCancelableEventHandler; position?: 'side' | 'bottom'; + onToggleFocusMode?: NonCancelableEventHandler<{ isExpanded: boolean }>; }; // Widgetization notice: structures in this file are shared multiple app layout instances, possibly different minor versions. diff --git a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx index 2590968f34..48a5e7f184 100644 --- a/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx +++ b/src/app-layout/visual-refresh-toolbar/state/use-app-layout.tsx @@ -71,7 +71,7 @@ export const useAppLayout = ( const [navigationAnimationDisabled, setNavigationAnimationDisabled] = useState(true); const [splitPanelAnimationDisabled, setSplitPanelAnimationDisabled] = useState(true); const [isNested, setIsNested] = useState(false); - const [expandedDrawerId, setExpandedDrawerId] = useState(null); + const [expandedDrawerId, setInternalExpandedDrawerId] = useState(null); const rootRefInternal = useRef(null); // This workaround ensures the ref is defined before checking if the app layout is nested. // On initial render, the ref might be undefined because this component loads asynchronously via the widget API. @@ -90,6 +90,16 @@ export const useAppLayout = ( fireNonCancelableEvent(onToolsChange, { open }); }; + const setExpandedDrawerId = (value: string | null) => { + setInternalExpandedDrawerId(value); + + if (aiDrawer?.onToggleFocusMode && (value === aiDrawer?.id || value === null)) { + fireNonCancelableEvent(aiDrawer.onToggleFocusMode, { + isExpanded: !!value, + }); + } + }; + const onGlobalDrawerFocus = (drawerId: string, open: boolean) => { globalDrawersFocusControl.setFocus({ force: true, drawerId, open }); }; diff --git a/src/internal/plugins/widget/interfaces.ts b/src/internal/plugins/widget/interfaces.ts index 5ffcaf7431..e9a919eee7 100644 --- a/src/internal/plugins/widget/interfaces.ts +++ b/src/internal/plugins/widget/interfaces.ts @@ -50,6 +50,7 @@ export interface DrawerPayload { unmountHeader?: (container: HTMLElement) => void; headerActions?: ReadonlyArray; onHeaderActionClick?: NonCancelableEventHandler; + onToggleFocusMode?: NonCancelableEventHandler<{ isExpanded: boolean }>; position?: 'side' | 'bottom'; }