diff --git a/pages/app-layout/multi-layout-with-hidden-instances-iframe.page.tsx b/pages/app-layout/multi-layout-with-hidden-instances-iframe.page.tsx index fda7d9f39e..3e848ec557 100644 --- a/pages/app-layout/multi-layout-with-hidden-instances-iframe.page.tsx +++ b/pages/app-layout/multi-layout-with-hidden-instances-iframe.page.tsx @@ -9,6 +9,7 @@ import Link from '~components/link'; import SideNavigation, { SideNavigationProps } from '~components/side-navigation'; import SpaceBetween from '~components/space-between'; +import './utils/external-widget'; import { IframeWrapper } from '../utils/iframe-wrapper'; import ScreenshotArea from '../utils/screenshot-area'; import { Breadcrumbs, Tools } from './utils/content-blocks'; @@ -18,7 +19,6 @@ function createView(name: string) { return function View() { return ( } @@ -55,6 +55,7 @@ export default function () { return ( { console.log('security drawer on toggle', event.detail); + awsuiPlugins.appLayout.updateDrawer({ id: 'security', defaultActive: event.detail.isOpen }); }, resizable: true, @@ -56,6 +57,7 @@ awsuiPlugins.appLayout.registerDrawer({ onResize: event => { setSizeRef.current?.(true); console.log('resize', event.detail); + awsuiPlugins.appLayout.updateDrawer({ id: 'security', defaultSize: event.detail.size }); }, mountContent: container => { @@ -165,7 +167,7 @@ awsuiPlugins.appLayout.registerDrawer({ }); awsuiPlugins.appLayout.registerDrawer({ - id: 'circle2-global', + id: 'global-with-stored-state', type: 'global', defaultActive: false, resizable: true, @@ -173,7 +175,7 @@ awsuiPlugins.appLayout.registerDrawer({ ariaLabels: { closeButton: 'Close button', - content: 'Content', + content: 'Drawer with counter', triggerButton: 'Trigger button', resizeHandle: 'Resize handle', }, @@ -185,10 +187,14 @@ awsuiPlugins.appLayout.registerDrawer({ `, }, + onToggle(event) { + awsuiPlugins.appLayout.updateDrawer({ id: 'global-with-stored-state', defaultActive: event.detail.isOpen }); + }, + mountContent: container => { ReactDOM.render( <> - + global widget content circle 2 , container diff --git a/pages/app-layout/with-drawers-scrollable.page.tsx b/pages/app-layout/with-drawers-scrollable.page.tsx index b29765b421..d25b7417b5 100644 --- a/pages/app-layout/with-drawers-scrollable.page.tsx +++ b/pages/app-layout/with-drawers-scrollable.page.tsx @@ -64,7 +64,7 @@ awsuiPlugins.appLayout.registerDrawer({ unmountContent: container => unmountComponentAtNode(container), }); awsuiPlugins.appLayout.registerDrawer({ - id: 'circle2-global', + id: 'global-with-stored-state', type: 'global', defaultActive: false, resizable: true, diff --git a/src/app-layout/__integ__/runtime-drawers.test.ts b/src/app-layout/__integ__/runtime-drawers.test.ts index 27e098fb65..4c565e9e1e 100644 --- a/src/app-layout/__integ__/runtime-drawers.test.ts +++ b/src/app-layout/__integ__/runtime-drawers.test.ts @@ -16,28 +16,30 @@ const findDrawerContentById = (wrapper: AppLayoutWrapper, id: string) => { }; describe.each(['classic', 'refresh', 'refresh-toolbar'] as Theme[])('%s', theme => { - function setupTest({ hasDrawers = 'false' }, testFn: (page: BasePageObject) => Promise) { - return useBrowser(async browser => { + function setupTest( + { hasDrawers = 'false', url = 'runtime-drawers', size = viewports.desktop }, + testFn: (page: BasePageObject) => Promise + ) { + return useBrowser({ width: size.width, height: size.height }, async browser => { const page = new BasePageObject(browser); await browser.url( - `#/light/app-layout/runtime-drawers?${getUrlParams(theme, { + `#/light/app-layout/${url}?${getUrlParams(theme, { hasDrawers: hasDrawers, hasTools: 'true', splitPanelPosition: 'side', })}` ); - await page.waitForVisible(wrapper.findDrawerTriggerById('security').toSelector(), true); + await page.waitForVisible(wrapper.findContentRegion().toSelector()); await testFn(page); }); } - //drawer width assertions not neccessary for mobile + //drawer width assertions not necessary for mobile describe('desktop', () => { test( 'should resize equally with tools or drawers', - setupTest({}, async page => { - await page.setWindowSize({ ...viewports.desktop, width: 1800 }); + setupTest({ size: { ...viewports.desktop, width: 1800 } }, async page => { await page.click(wrapper.findToolsToggle().toSelector()); await page.click(wrapper.findSplitPanel().findOpenButton().toSelector()); @@ -53,7 +55,6 @@ describe.each(['classic', 'refresh', 'refresh-toolbar'] as Theme[])('%s', theme test( 'renders according to defaultSize property', setupTest({}, async page => { - await page.setWindowSize(viewports.desktop); await page.click(wrapper.findDrawerTriggerById('security').toSelector()); // using `clientWidth` to neglect possible border width set on this element const width = await page.getElementProperty(wrapper.findActiveDrawer().toSelector(), 'clientWidth'); @@ -64,7 +65,6 @@ describe.each(['classic', 'refresh', 'refresh-toolbar'] as Theme[])('%s', theme test( 'should call resize handler', setupTest({}, async page => { - await page.setWindowSize(viewports.desktop); // close navigation panel to give drawer more room to resize await page.click(wrapper.findNavigationClose().toSelector()); await page.click(wrapper.findDrawerTriggerById('security').toSelector()); @@ -76,10 +76,38 @@ describe.each(['classic', 'refresh', 'refresh-toolbar'] as Theme[])('%s', theme }) ); + test( + 'should persist runtime drawer preferences when switching between multiple app layouts', + setupTest( + { + url: 'multi-layout-with-hidden-instances-iframe', + }, + async page => { + await page.runInsideIframe('#page1', theme !== 'refresh-toolbar', async () => { + await page.click(wrapper.findDrawerTriggerById('security').toSelector()); + }); + let newWidth: number; + await page.runInsideIframe('#page1', true, async () => { + await expect(page.getText(wrapper.findActiveDrawer().toSelector())).resolves.toContain('Security'); + const { width: originalWidth } = await page.getBoundingBox(wrapper.findActiveDrawer().toSelector()); + await page.dragAndDrop(wrapper.findActiveDrawerResizeHandle().toSelector(), -200); + ({ width: newWidth } = await page.getBoundingBox(wrapper.findActiveDrawer().toSelector())); + expect(newWidth).toBeGreaterThan(originalWidth); + }); + + await page.click(wrapper.findNavigation().findSideNavigation().findLinkByHref('page2').toSelector()); + await page.runInsideIframe('#page2', true, async () => { + await page.waitForVisible(wrapper.findActiveDrawer().toSelector()); + expect((await page.getBoundingBox(wrapper.findActiveDrawer().toSelector())).width).toEqual(newWidth!); + await expect(page.getText(wrapper.findActiveDrawer().toSelector())).resolves.toContain('Security'); + }); + } + ) + ); + test( 'should show sticky elements on scroll in drawer', setupTest({ hasDrawers: 'true' }, async page => { - await page.setWindowSize(viewports.desktop); await page.waitForVisible(wrapper.findDrawerTriggerById('pro-help').toSelector(), true); await expect(page.isDisplayed('[data-testid="drawer-sticky-footer"]')).resolves.toBe(false); @@ -151,11 +179,13 @@ describe('Visual refresh toolbar only', () => { await page.setWindowSize({ ...viewports.desktop, width: 1700 }); await page.click(wrapper.findDrawerTriggerById('security').toSelector()); await page.click(wrapper.findDrawerTriggerById('circle-global').toSelector()); - await page.click(wrapper.findDrawerTriggerById('circle2-global').toSelector()); + await page.click(wrapper.findDrawerTriggerById('global-with-stored-state').toSelector()); await expect(page.isClickable(findDrawerById(wrapper, 'security')!.toSelector())).resolves.toBe(true); await expect(page.isClickable(findDrawerById(wrapper, 'circle-global')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); }) ); @@ -165,7 +195,7 @@ describe('Visual refresh toolbar only', () => { setupTest(async page => { await page.setWindowSize({ ...viewports.desktop, width: 1600 }); await page.click(wrapper.findDrawerTriggerById('circle-global').toSelector()); - await page.click(wrapper.findDrawerTriggerById('circle2-global').toSelector()); + await page.click(wrapper.findDrawerTriggerById('global-with-stored-state').toSelector()); // resize an active drawer to take up all available space await page.dragAndDrop(wrapper.findActiveDrawerResizeHandle().toSelector(), -600); @@ -174,7 +204,9 @@ describe('Visual refresh toolbar only', () => { await expect(page.isClickable(findDrawerById(wrapper, 'security')!.toSelector())).resolves.toBe(true); await expect(page.isClickable(findDrawerById(wrapper, 'circle-global')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); }) ); @@ -183,12 +215,14 @@ describe('Visual refresh toolbar only', () => { setupTest(async page => { await page.setWindowSize({ ...viewports.desktop, width: 1400 }); await page.click(wrapper.findDrawerTriggerById('circle-global').toSelector()); - await page.click(wrapper.findDrawerTriggerById('circle2-global').toSelector()); + await page.click(wrapper.findDrawerTriggerById('global-with-stored-state').toSelector()); await page.click(wrapper.findDrawerTriggerById('security').toSelector()); await expect(page.isClickable(findDrawerById(wrapper, 'circle-global')!.toSelector())).resolves.toBe(false); await expect(page.isClickable(findDrawerById(wrapper, 'security')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); }) ); @@ -199,12 +233,14 @@ describe('Visual refresh toolbar only', () => { await page.click(wrapper.findDrawerTriggerById('circle').toSelector()); await page.click(wrapper.findDrawerTriggerById('security').toSelector()); await page.click(wrapper.findDrawerTriggerById('circle-global').toSelector()); - await page.click(wrapper.findDrawerTriggerById('circle2-global').toSelector()); + await page.click(wrapper.findDrawerTriggerById('global-with-stored-state').toSelector()); await expect(page.isClickable(findDrawerById(wrapper, 'circle')!.toSelector())).resolves.toBe(false); await expect(page.isClickable(findDrawerById(wrapper, 'security')!.toSelector())).resolves.toBe(false); await expect(page.isClickable(findDrawerById(wrapper, 'circle-global')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); }) ); }); @@ -214,12 +250,14 @@ describe('Visual refresh toolbar only', () => { setupTest(async page => { await page.setWindowSize({ ...viewports.desktop, width: 1600 }); await page.click(wrapper.findDrawerTriggerById('circle').toSelector()); - await page.click(wrapper.findDrawerTriggerById('circle2-global').toSelector()); + await page.click(wrapper.findDrawerTriggerById('global-with-stored-state').toSelector()); await page.click(wrapper.findDrawerTriggerById('circle3-global').toSelector()); await expect(page.isClickable(wrapper.findNavigation().toSelector())).resolves.toBe(true); await expect(page.isClickable(findDrawerById(wrapper, 'circle')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); await expect(page.isClickable(findDrawerById(wrapper, 'circle3-global')!.toSelector())).resolves.toBe(true); await expect(page.hasHorizontalScroll()).resolves.toBe(false); @@ -227,7 +265,9 @@ describe('Visual refresh toolbar only', () => { // navigation panel closes first await expect(page.isClickable(wrapper.findNavigation().toSelector())).resolves.toBe(false); await expect(page.isClickable(findDrawerById(wrapper, 'circle')!.toSelector())).resolves.toBe(true); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); await expect(page.isClickable(findDrawerById(wrapper, 'circle3-global')!.toSelector())).resolves.toBe(true); await expect(page.hasHorizontalScroll()).resolves.toBe(false); @@ -235,7 +275,9 @@ describe('Visual refresh toolbar only', () => { // then the first opened drawer closes await expect(page.isClickable(wrapper.findNavigation().toSelector())).resolves.toBe(false); await expect(page.isClickable(findDrawerById(wrapper, 'circle')!.toSelector())).resolves.toBe(false); - await expect(page.isClickable(findDrawerById(wrapper, 'circle2-global')!.toSelector())).resolves.toBe(true); + await expect(page.isClickable(findDrawerById(wrapper, 'global-with-stored-state')!.toSelector())).resolves.toBe( + true + ); await expect(page.isClickable(findDrawerById(wrapper, 'circle3-global')!.toSelector())).resolves.toBe(true); await expect(page.hasHorizontalScroll()).resolves.toBe(false); }) diff --git a/src/app-layout/utils/use-pointer-events.ts b/src/app-layout/utils/use-pointer-events.ts index 8de8d1762d..f526125cb0 100644 --- a/src/app-layout/utils/use-pointer-events.ts +++ b/src/app-layout/utils/use-pointer-events.ts @@ -41,22 +41,31 @@ export const usePointerEvents = ({ position, panelRef, handleRef, onResize }: Si ); const onDocumentPointerUp = useCallback(() => { - if (!panelRef || !panelRef.current) { + const panelElement = panelRef?.current; + /* istanbul ignore if */ + if (!panelElement) { return; } + const currentDocument = panelElement.ownerDocument; - document.body.classList.remove(styles['resize-active']); - document.body.classList.remove(styles[`resize-${position}`]); - document.removeEventListener('pointerup', onDocumentPointerUp); - document.removeEventListener('pointermove', onDocumentPointerMove); + currentDocument.body.classList.remove(styles['resize-active']); + currentDocument.body.classList.remove(styles[`resize-${position}`]); + currentDocument.removeEventListener('pointerup', onDocumentPointerUp); + currentDocument.removeEventListener('pointermove', onDocumentPointerMove); }, [panelRef, onDocumentPointerMove, position]); const onSliderPointerDown = useCallback(() => { - document.body.classList.add(styles['resize-active']); - document.body.classList.add(styles[`resize-${position}`]); - document.addEventListener('pointerup', onDocumentPointerUp); - document.addEventListener('pointermove', onDocumentPointerMove); - }, [onDocumentPointerMove, onDocumentPointerUp, position]); + const panelElement = panelRef?.current; + /* istanbul ignore if */ + if (!panelElement) { + return; + } + const currentDocument = panelElement.ownerDocument; + currentDocument.body.classList.add(styles['resize-active']); + currentDocument.body.classList.add(styles[`resize-${position}`]); + currentDocument.addEventListener('pointerup', onDocumentPointerUp); + currentDocument.addEventListener('pointermove', onDocumentPointerMove); + }, [panelRef, onDocumentPointerMove, onDocumentPointerUp, position]); return onSliderPointerDown; };