From bbc63458398902e78526774281b93562181f63fe Mon Sep 17 00:00:00 2001 From: "myelyn.kim" Date: Fri, 11 Oct 2024 10:58:48 +0900 Subject: [PATCH] WRR-976: ContextualPopupDecorator: Modified to update position when screen orientation change Enact-DCO-1.0-Signed-off-by: Hyelyn Kim (myelyn.kim@lge.com) --- CHANGELOG.md | 6 ++++ .../ContextualPopupDecorator.js | 26 ++++++++++++++ .../tests/ContextualPopupDecorator-specs.js | 36 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9b3c2110..951c37a180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The following is a curated list of changes in the Enact sandstone module, newest changes on the top. +## [unreleased] + +### Fixed + +- `sandstone/ContextualPopupDecorator` to update popup position properly when the screen orientation change + ## [2.7.18] - 2024-09-05 ### Added diff --git a/ContextualPopupDecorator/ContextualPopupDecorator.js b/ContextualPopupDecorator/ContextualPopupDecorator.js index 85d2fc068d..104df5e991 100644 --- a/ContextualPopupDecorator/ContextualPopupDecorator.js +++ b/ContextualPopupDecorator/ContextualPopupDecorator.js @@ -1,3 +1,5 @@ +/* global ResizeObserver */ + /** * A higher-order component to add a Sandstone styled popup to a component. * @@ -271,6 +273,7 @@ const Decorator = hoc(defaultConfig, (config, Wrapped) => { activator: null }; + this.resizeObserver = null; this.overflow = {}; this.adjustedDirection = this.props.direction; this.id = this.generateId(); @@ -290,6 +293,12 @@ const Decorator = hoc(defaultConfig, (config, Wrapped) => { on('keydown', this.handleKeyDown); on('keyup', this.handleKeyUp); } + + if (typeof ResizeObserver === 'function') { + this.resizeObserver = new ResizeObserver(() => { + this.positionContextualPopup(); + }); + } } getSnapshotBeforeUpdate (prevProps, prevState) { @@ -339,6 +348,11 @@ const Decorator = hoc(defaultConfig, (config, Wrapped) => { off('keyup', this.handleKeyUp); } Spotlight.remove(this.state.containerId); + + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } } generateId = () => { @@ -594,6 +608,18 @@ const Decorator = hoc(defaultConfig, (config, Wrapped) => { getClientNode = (node) => { this.clientNode = ReactDOM.findDOMNode(node); // eslint-disable-line react/no-find-dom-node + + if (this.resizeObserver) { + if (node) { + // It is not easy to trigger changed position of activator, + // so we chose to observe the `div` element's size that has the real size below the root of floatLayer. + // This implementation is dependent on the current structure of FloatingLayer, + // so if the structure have changed, below code needs to be changed accordingly. + this.resizeObserver.observe(node?.parentElement?.parentElement); + } else { + this.resizeObserver.disconnect(); + } + } }; handle = handle.bind(this); diff --git a/ContextualPopupDecorator/tests/ContextualPopupDecorator-specs.js b/ContextualPopupDecorator/tests/ContextualPopupDecorator-specs.js index 03a6562776..2c4e625e40 100644 --- a/ContextualPopupDecorator/tests/ContextualPopupDecorator-specs.js +++ b/ContextualPopupDecorator/tests/ContextualPopupDecorator-specs.js @@ -505,4 +505,40 @@ describe('ContextualPopupDecorator Specs', () => { expect(scrimDivFirst).toHaveClass(expectedFirst); expect(scrimDivSecond).toHaveClass(expectedSecond); }); + + test('should create and observe with `ResizeObserver` when the popup opened and disconnect when the popup closed', () => { + const originalObserver = global.ResizeObserver; + + const MockObserverInstance = { + observe: jest.fn(), + disconnect: jest.fn() + }; + global.ResizeObserver = jest.fn().mockImplementation(() => MockObserverInstance); + + const Root = FloatingLayerDecorator('div'); + const {rerender} = render( + +
}> + Hello +
+
+ ); + + const contextualButton = screen.getByTestId('contextualButton'); + + expect(contextualButton).toBeInTheDocument(); + expect(MockObserverInstance.observe).toHaveBeenCalled(); + + rerender( + +
}> + Hello +
+
+ ); + + expect(MockObserverInstance.disconnect).toHaveBeenCalled(); + + global.ResizeObserver = originalObserver; + }); });