Skip to content
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
29 changes: 25 additions & 4 deletions src/ViewerCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import classnames from 'classnames';

function noop() { }

// const transitionDuration = 300;

const ACTION_TYPES = {
setVisible: 'setVisible',
setActiveIndex: 'setActiveIndex',
Expand Down Expand Up @@ -173,6 +171,28 @@ export default (props: ViewerProps) => {
}
}, [visible]);

React.useEffect(() => {
if (visible || !state.visible) {
return;
}
// A disabled or interrupted transition may never emit transitionend.
const style = window.getComputedStyle(viewerCore.current);
const toMilliseconds = (value: string) => (parseFloat(value) || 0) * (value.trim().endsWith('ms') ? 1 : 1000);
const durations = style.getPropertyValue('transition-duration').split(',').map(toMilliseconds);
const delays = style.getPropertyValue('transition-delay').split(',').map(toMilliseconds);
const properties = style.getPropertyValue('transition-property').split(',');
const timeout = properties.reduce((longest, property, index) => {
if (property.trim() !== 'opacity' && property.trim() !== 'all') {
return longest;
}
return Math.max(longest, durations[index % durations.length] + delays[index % delays.length]);
}, 0);
const timer = window.setTimeout(() => {
dispatch(createAction(ACTION_TYPES.setVisible, { visible: false }));
}, timeout + 50);
return () => window.clearTimeout(timer);
}, [visible, state.visible]);

React.useEffect(() => {
bindEvent();

Expand Down Expand Up @@ -642,6 +662,7 @@ export default (props: ViewerProps) => {
let viewerStryle: React.CSSProperties = {
opacity: (visible && state.visible) ? 1 : 0,
display: (visible || state.visible) ? 'block' : 'none',
pointerEvents: visible ? undefined : 'none',
};

let activeImg: ImageDecorator = {
Expand All @@ -657,8 +678,8 @@ export default (props: ViewerProps) => {
<div
className={className}
style={viewerStryle}
onTransitionEnd={() => {
if (!visible) {
onTransitionEnd={(event) => {
if (!visible && event.target === event.currentTarget && event.propertyName === 'opacity') {
dispatch(createAction(ACTION_TYPES.setVisible, {
visible: false,
}));
Expand Down
66 changes: 66 additions & 0 deletions src/__tests__/viewer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ViewerProps from '../ViewerProps';
import { configure, mount } from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import * as React from 'react';
import { act } from 'react-dom/test-utils';
const img2 = require('../../demo/images/landscape2.jpg');
const img = require('../../demo/images/landscape.jpg');

Expand Down Expand Up @@ -241,6 +242,71 @@ describe('Viewer', () => {
expect($$('.react-viewer')[0].style.display).toBe('none');
});

it('hides the closed viewer when no transitionend event arrives', () => {
viewerHelper.new();
viewerHelper.open();
wrapper.find('.react-viewer-close').simulate('click');
viewerHelper.skipAnimation();
expect($$('.react-viewer')[0].style.display).toBe('none');
});

it('does not let an old close timer hide a reopened viewer', () => {
viewerHelper.new();
viewerHelper.open();
wrapper.find('.react-viewer-close').simulate('click');
wrapper.find('#viewer-tester-open-btn').simulate('click');
viewerHelper.skipAnimation();
expect($$('.react-viewer')[0].style.display).toBe('block');
expect($$('.react-viewer')[0].style.opacity).toBe('1');
});

it('waits for the configured opacity transition before fallback cleanup', () => {
viewerHelper.new();
viewerHelper.open();
const viewer = $$('.react-viewer')[0] as HTMLElement;
// This legacy jsdom does not implement CSS transition properties.
const computedStyle = window.getComputedStyle;
const styleSpy = jest.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
if (element === viewer) {
return { getPropertyValue: (property) => ({
'transition-property': 'transform, opacity',
'transition-duration': '100ms, 0.6s',
'transition-delay': '0.1s',
}[property] || '') } as CSSStyleDeclaration;
}
return computedStyle(element);
});
try {
wrapper.find('.react-viewer-close').simulate('click');
} finally {
styleSpy.mockRestore();
}
expect(viewer.style.pointerEvents).toBe('none');
act(() => { jest.advanceTimersByTime(700); });
expect(viewer.style.display).toBe('block');
act(() => { jest.advanceTimersByTime(50); });
expect(viewer.style.display).toBe('none');
});

it('only finishes closing for the root opacity transition', () => {
viewerHelper.new();
viewerHelper.open();
const viewer = $$('.react-viewer')[0] as HTMLElement;
wrapper.find('.react-viewer-close').simulate('click');
wrapper.find('.react-viewer').simulate('transitionend', {
target: $$('.react-viewer-footer')[0], currentTarget: viewer, propertyName: 'opacity',
});
expect(viewer.style.display).toBe('block');
wrapper.find('.react-viewer').simulate('transitionend', {
target: viewer, currentTarget: viewer, propertyName: 'transform',
});
expect(viewer.style.display).toBe('block');
wrapper.find('.react-viewer').simulate('transitionend', {
target: viewer, currentTarget: viewer, propertyName: 'opacity',
});
expect(viewer.style.display).toBe('none');
});

it('exposes viewer controls to keyboard users', () => {
viewerHelper.new();
viewerHelper.open();
Expand Down
Loading