diff --git a/__tests__/src/extend/withSize.test.js b/__tests__/src/extend/withSize.test.js new file mode 100644 index 0000000000..ece8055b56 --- /dev/null +++ b/__tests__/src/extend/withSize.test.js @@ -0,0 +1,50 @@ +import { render, screen } from '@testing-library/react'; +import PropTypes from 'prop-types'; +import { withSize } from '../../../src/extend/withSize'; + +/** Mock ResizeObserver */ +class ResizeObserver { + /** */ + constructor(callback) { + this.callback = callback; + } + + /** */ + observe(element) { + // Fake a resize event + setTimeout(() => { + this.callback([{ contentRect: { height: 300, width: 400 } }]); + }, 0); + } + + /** */ + disconnect() { jest.fn(); } // eslint-disable-line +} + +// Replace the global ResizeObserver with the mock +global.ResizeObserver = ResizeObserver; + +/** */ +const TestComponent = ({ size }) => ( +
+ {size.width} + {size.height} +
+); + +TestComponent.propTypes = { + size: PropTypes.shape({ + height: PropTypes.number, + width: PropTypes.number, + }).isRequired, +}; + +const WrappedTestComponent = withSize()(TestComponent); + +test('it should render with size', async () => { + render(); + + // Assert that the updated size is reflected + expect(await screen.findByText(/400/)).toBeInTheDocument(); + expect(await screen.findByText(/300/)).toBeInTheDocument(); +}); diff --git a/package.json b/package.json index cbc145d4ef..aca9e491d0 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "react-redux": "^8.0.0 || ^9.0.0", "react-resize-observer": "^1.1.1", "react-rnd": "^10.1", - "react-sizeme": "^2.6.7 || ^3.0.0", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.5", "redux": "^5.0.0", diff --git a/setupJest.js b/setupJest.js index d5a0664075..af9f272bcf 100644 --- a/setupJest.js +++ b/setupJest.js @@ -1,18 +1,22 @@ /* eslint-disable import/no-extraneous-dependencies */ import fetchMock from 'jest-fetch-mock'; -import sizeMe from 'react-sizeme'; import i18next from 'i18next'; import { setupIntersectionMocking } from 'react-intersection-observer/test-utils'; import en from './src/locales/en/translation.json'; jest.setTimeout(10000); -sizeMe.noPlaceholders = true; - const { TextEncoder } = require('util'); global.TextEncoder = TextEncoder; +// Mock the browser's native ResizeObserver +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), +})); + // Setup Jest to mock fetch fetchMock.enableMocks(); diff --git a/src/components/CompanionWindow.js b/src/components/CompanionWindow.js index 8a31ce2fb1..3b0d04e28d 100644 --- a/src/components/CompanionWindow.js +++ b/src/components/CompanionWindow.js @@ -1,4 +1,4 @@ -import { Children, cloneElement, Component } from 'react'; +import { Children, cloneElement } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; import CloseIcon from '@mui/icons-material/CloseSharp'; @@ -23,17 +23,17 @@ const StyledCloseButton = styled(MiradorMenuButton, { name: 'CompanionWindow', s /** * CompanionWindow */ -export class CompanionWindow extends Component { +export function CompanionWindow(props) { /** */ - openInNewStyle() { - const { direction } = this.props; + const openInNewStyle = () => { + const { direction } = props; if (direction === 'rtl') return { transform: 'scale(-1, 1)' }; return {}; - } + }; /** */ - resizeHandles() { - const { direction, position } = this.props; + const resizeHandles = () => { + const { direction, position } = props; const positions = { ltr: { default: 'left', @@ -69,126 +69,119 @@ export class CompanionWindow extends Component { } return base; - } + }; + const { + ariaLabel, classes, paperClassName, onCloseClick, updateCompanionWindow, isDisplayed, + position, t, title, children, titleControls, size, + defaultSidebarPanelWidth, defaultSidebarPanelHeight, innerRef, + } = props; - /** - * render - * @return - */ - render() { - const { - ariaLabel, classes, paperClassName, onCloseClick, updateCompanionWindow, isDisplayed, - position, t, title, children, titleControls, size, - defaultSidebarPanelWidth, defaultSidebarPanelHeight, innerRef, - } = this.props; + const isBottom = (position === 'bottom' || position === 'far-bottom'); - const isBottom = (position === 'bottom' || position === 'far-bottom'); - - const childrenWithAdditionalProps = Children.map(children, (child) => { - if (!child) return null; - return cloneElement( - child, - { - parentactions: { - closeCompanionWindow: onCloseClick, - }, + const childrenWithAdditionalProps = Children.map(children, (child) => { + if (!child) return null; + return cloneElement( + child, + { + parentactions: { + closeCompanionWindow: onCloseClick, }, - ); - }); + }, + ); + }); - return ( - + - - - {title} - { - position === 'left' - ? updateCompanionWindow - && ( - { updateCompanionWindow({ position: 'right' }); }} - > - - - ) - : ( - <> - { - updateCompanionWindow && ( - { updateCompanionWindow({ position: position === 'bottom' ? 'right' : 'bottom' }); }} - > - - - ) - } - - - - - ) - } - { - titleControls && ( - + {title} + { + position === 'left' + ? updateCompanionWindow + && ( + { updateCompanionWindow({ position: 'right' }); }} > - {titleControls} - + + ) - } - - - {childrenWithAdditionalProps} - - - - ); - } + : ( + <> + { + updateCompanionWindow && ( + { updateCompanionWindow({ position: position === 'bottom' ? 'right' : 'bottom' }); }} + > + + + ) + } + + + + + ) + } + { + titleControls && ( + + {titleControls} + + ) + } + + + {childrenWithAdditionalProps} + + + + ); } CompanionWindow.propTypes = { @@ -224,7 +217,7 @@ CompanionWindow.defaultProps = { defaultSidebarPanelWidth: 235, innerRef: undefined, isDisplayed: false, - onCloseClick: () => {}, + onCloseClick: () => { }, paperClassName: '', position: null, size: {}, diff --git a/src/containers/CompanionWindow.js b/src/containers/CompanionWindow.js index 64a1806440..4747d9c166 100644 --- a/src/containers/CompanionWindow.js +++ b/src/containers/CompanionWindow.js @@ -1,7 +1,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; -import { withSize } from 'react-sizeme'; +import { withSize } from '../extend/withSize'; import { withPlugins } from '../extend/withPlugins'; import { withRef } from '../extend/withRef'; import * as actions from '../state/actions'; @@ -46,8 +46,8 @@ const mapDispatchToProps = (dispatch, { windowId, id }) => ({ const enhance = compose( withRef(), - withTranslation(), withSize(), + withTranslation(), connect(mapStateToProps, mapDispatchToProps), withPlugins('CompanionWindow'), ); diff --git a/src/containers/WindowCanvasNavigationControls.js b/src/containers/WindowCanvasNavigationControls.js index ca5088f34b..91b62e584c 100644 --- a/src/containers/WindowCanvasNavigationControls.js +++ b/src/containers/WindowCanvasNavigationControls.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { compose } from 'redux'; -import { withSize } from 'react-sizeme'; +import { withSize } from '../extend/withSize'; import { withPlugins } from '../extend/withPlugins'; import { getShowZoomControlsConfig, getWorkspace } from '../state/selectors'; import { WindowCanvasNavigationControls } from '../components/WindowCanvasNavigationControls'; diff --git a/src/extend/withSize.js b/src/extend/withSize.js new file mode 100644 index 0000000000..14f2d136e9 --- /dev/null +++ b/src/extend/withSize.js @@ -0,0 +1,47 @@ +/** This file was written to replace https://github.com/ctrlplusb/react-sizeme + * when its dependencies went out of date and is very much inspired by its code. + */ + +import { useEffect, useRef, useState } from 'react'; + +/** */ +export function withSize() { + return function WrapComponent(WrappedComponent) { + /** */ + const SizeAwareComponent = (props) => { + const [size, setSize] = useState({ height: undefined, width: undefined }); + const elementRef = useRef(null); + const observerRef = useRef(null); + + useEffect(() => { + /** */ + const handleResize = (entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + setSize({ height, width }); + } + }; + + observerRef.current = new ResizeObserver(handleResize); + + if (elementRef.current) { + observerRef.current.observe(elementRef.current); + } + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + } + }; + }, []); + + return ( +
+ +
+ ); + }; + + return SizeAwareComponent; + }; +}