Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed issue with EuiResizeObserver fallback #3088

Merged
merged 7 commits into from
Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
**Bug Fixes**

- Fixed race condition in `EuiIcon` when switching from dynamically fetched components ([#3118](https://github.com/elastic/eui/pull/3118))
- Fixed the issue that `EuiResizeObserver` fallback did not properly listen to pure window resizing ([#3088](https://github.com/elastic/eui/pull/3088))

## [`21.1.0`](https://github.com/elastic/eui/tree/v21.1.0)

Expand Down
126 changes: 65 additions & 61 deletions src/components/observer/resize_observer/resize_observer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,10 @@ export async function waitforResizeObserver(period = 30) {
await sleep(period);
}

describe('EuiResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);
const onResize = jest.fn();

const Wrapper: FunctionComponent<{}> = ({ children }) => {
return (
<EuiResizeObserver onResize={onResize}>
{(resizeRef: (e: HTMLElement | null) => void) => (
<div ref={resizeRef}>{children}</div>
)}
</EuiResizeObserver>
);
};

const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Resize observer is expected to fire once on mount
await waitforResizeObserver();
expect(onResize).toHaveBeenCalledTimes(1);

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

await waitforResizeObserver();

// Expect 2 calls because it's called once on mount
expect(onResize).toHaveBeenCalledTimes(2);
});
});

type GetBoundingClientRect = typeof HTMLElement['prototype']['getBoundingClientRect'];
describe('useResizeObserver', () => {
describe('testResizeObservers', () => {
// refactor the tests structure to make sure that `EuiResizeObserver` test can get
// the proper size of the dom element.
type GetBoundingClientRect = typeof HTMLElement['prototype']['getBoundingClientRect'];
let _originalgetBoundingClientRect: undefined | GetBoundingClientRect;
beforeAll(() => {
_originalgetBoundingClientRect =
Expand All @@ -64,33 +29,72 @@ describe('useResizeObserver', () => {
HTMLElement.prototype.getBoundingClientRect = _originalgetBoundingClientRect!;
});

it('watches for a resize', async () => {
expect.assertions(2);

const Wrapper: FunctionComponent<{}> = jest.fn(({ children }) => {
const [ref, setRef] = useState();
useResizeObserver(ref);
return <div ref={setRef}>{children}</div>;
describe('EuiResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);
const onResize = jest.fn();

const Wrapper: FunctionComponent<{}> = ({ children }) => {
return (
<EuiResizeObserver onResize={onResize}>
{(resizeRef: (e: HTMLElement | null) => void) => (
<div ref={resizeRef}>{children}</div>
)}
</EuiResizeObserver>
);
};

const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Resize observer is expected to fire once on mount
await waitforResizeObserver();
expect(onResize).toHaveBeenCalledTimes(1);

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

await waitforResizeObserver();

// Expect 2 calls because it's called once on mount
expect(onResize).toHaveBeenCalledTimes(2);
});
});

const component = mount(<Wrapper children={<div>Hello World</div>} />);
describe('useResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);

// Expect the initial render, re-render when the ref is created, and a 3rd for the onresize callback
await act(() => waitforResizeObserver());
expect(Wrapper).toHaveBeenCalledTimes(3);
const Wrapper: FunctionComponent<{}> = jest.fn(({ children }) => {
const [ref, setRef] = useState();
useResizeObserver(ref);
return <div ref={setRef}>{children}</div>;
});

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});
const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Expect the initial render, re-render when the ref is created, and a 3rd for the onresize callback
await act(() => waitforResizeObserver());
expect(Wrapper).toHaveBeenCalledTimes(3);

await waitforResizeObserver();
component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

// Expect two more calls because children changed (re-render) & resize observer reacted
expect(Wrapper).toHaveBeenCalledTimes(5);
await waitforResizeObserver();

// Expect two more calls because children changed (re-render) & resize observer reacted
expect(Wrapper).toHaveBeenCalledTimes(5);
});
});
});
29 changes: 27 additions & 2 deletions src/components/observer/resize_observer/resize_observer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,25 @@ const mutationObserverOptions = {
export class EuiResizeObserver extends EuiObserver<Props> {
name = 'EuiResizeObserver';

state = {
height: 0,
width: 0,
};

onResize = () => {
if (this.childNode != null) {
// Eventually use `clientRect` on the `entries[]` returned natively
const { height, width } = this.childNode.getBoundingClientRect();
// Check for actual resize event
if (this.state.height === height && this.state.width === width) {
return;
}

this.props.onResize({
height,
width,
});
this.setState({ height, width });
}
};

Expand All @@ -40,14 +51,28 @@ export class EuiResizeObserver extends EuiObserver<Props> {
};
}

const makeCompatibleObserver = (node: Element, callback: () => void) => {
const observer = new MutationObserver(callback);
observer.observe(node, mutationObserverOptions);

window.addEventListener('resize', callback);

observer.disconnect = () => {
observer.disconnect();

window.removeEventListener('resize', callback);
};

return observer;
};

const makeResizeObserver = (node: Element, callback: () => void) => {
let observer: Observer | undefined;
if (hasResizeObserver) {
observer = new window.ResizeObserver(callback);
observer.observe(node);
} else {
observer = new MutationObserver(callback);
observer.observe(node, mutationObserverOptions);
observer = makeCompatibleObserver(node, callback);
requestAnimationFrame(callback); // Mimic ResizeObserver behavior of triggering a resize event on init
}
return observer;
Expand Down