Skip to content

Commit 41614ec

Browse files
authored
feat: re-calc pos when window resize (#17)
* feat: re-clac pos when window resize * feat: add test cases & prettier code * feat: remove redundant code * feat: update test case * style: 撤回格式化代码 * feat: code optimize * feat: code optimize
1 parent 5513b08 commit 41614ec

File tree

5 files changed

+254
-4
lines changed

5 files changed

+254
-4
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ const config = {
88
},
99
};
1010

11-
module.exports = config;
11+
module.exports = config;

src/hooks/useTarget.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useMemo } from 'react';
22
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
33
import { isInViewPort } from '../util';
44
import type { TourStepInfo } from '..';
5+
import useEvent from "rc-util/lib/hooks/useEvent";
56

67
export interface Gap {
78
offset?: number;
@@ -37,7 +38,7 @@ export default function useTarget(
3738
// ========================= Align ==========================
3839
const [posInfo, setPosInfo] = useState<PosInfo>(null);
3940

40-
useLayoutEffect(() => {
41+
const updatePos = useEvent(() => {
4142
if (targetElement) {
4243
// Exist target element. We should scroll and get target position
4344
if (!isInViewPort(targetElement)) {
@@ -52,14 +53,22 @@ export default function useTarget(
5253
if (JSON.stringify(origin) !== JSON.stringify(nextPosInfo)) {
5354
return nextPosInfo;
5455
}
55-
5656
return origin;
5757
});
5858
} else {
5959
// Not exist target which means we just show in center
6060
setPosInfo(null);
6161
}
62-
}, [targetElement, open]);
62+
});
63+
64+
useLayoutEffect(() => {
65+
updatePos();
66+
// update when window resize
67+
window.addEventListener('resize', updatePos);
68+
return () => {
69+
window.removeEventListener('resize', updatePos);
70+
}
71+
}, [targetElement, open, updatePos]);
6372

6473
// ======================== PosInfo =========================
6574
const mergedPosInfo = useMemo(() => {

tests/__snapshots__/index.test.tsx.snap

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,163 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Tour should update position when window resize 1`] = `
4+
<body>
5+
<div>
6+
<div
7+
style="width: 100%;"
8+
>
9+
<button
10+
class="btn2"
11+
>
12+
按钮
13+
</button>
14+
</div>
15+
</div>
16+
<div
17+
style="position: absolute; top: 0px; left: 0px; width: 100%;"
18+
>
19+
<div>
20+
<div
21+
class="rc-tour"
22+
style="z-index: 1090; opacity: 0;"
23+
>
24+
<div
25+
class="rc-tour-content"
26+
>
27+
<div
28+
class="rc-tour-arrow"
29+
/>
30+
<div
31+
class="rc-tour-inner"
32+
>
33+
<button
34+
aria-label="Close"
35+
class="rc-tour-close"
36+
type="button"
37+
>
38+
<span
39+
class="rc-tour-close-x"
40+
>
41+
×
42+
</span>
43+
</button>
44+
<div
45+
class="rc-tour-header"
46+
>
47+
<div
48+
class="rc-tour-title"
49+
>
50+
创建
51+
</div>
52+
</div>
53+
<div
54+
class="rc-tour-description"
55+
>
56+
创建一条数据
57+
</div>
58+
<div
59+
class="rc-tour-footer"
60+
>
61+
<div
62+
class="rc-tour-sliders"
63+
/>
64+
<div
65+
class="rc-tour-buttons"
66+
>
67+
<button
68+
class="rc-tour-finish-btn"
69+
>
70+
Finish
71+
</button>
72+
</div>
73+
</div>
74+
</div>
75+
</div>
76+
</div>
77+
</div>
78+
</div>
79+
<div>
80+
<div
81+
class="rc-tour-target-placeholder"
82+
style="left: 9px; top: 4px; width: 242px; height: 192px; position: fixed; pointer-events: none;"
83+
/>
84+
</div>
85+
<div>
86+
<div
87+
class="rc-tour-mask"
88+
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 900; pointer-events: none;"
89+
>
90+
<svg
91+
style="width: 100%; height: 100%;"
92+
>
93+
<defs>
94+
<mask
95+
id="rc-tour-mask-test-id"
96+
>
97+
<rect
98+
fill="white"
99+
height="100%"
100+
width="100%"
101+
x="0"
102+
y="0"
103+
/>
104+
<rect
105+
class=""
106+
fill="black"
107+
height="192"
108+
rx="2"
109+
width="242"
110+
x="9"
111+
y="4"
112+
/>
113+
</mask>
114+
</defs>
115+
<rect
116+
fill="rgba(0,0,0,0.5)"
117+
height="100%"
118+
mask="url(#rc-tour-mask-test-id)"
119+
width="100%"
120+
x="0"
121+
y="0"
122+
/>
123+
<rect
124+
fill="transparent"
125+
height="4"
126+
pointer-events="auto"
127+
width="100%"
128+
x="0"
129+
y="0"
130+
/>
131+
<rect
132+
fill="transparent"
133+
height="100%"
134+
pointer-events="auto"
135+
width="9"
136+
x="0"
137+
y="0"
138+
/>
139+
<rect
140+
fill="transparent"
141+
height="calc(100vh - 196px)"
142+
pointer-events="auto"
143+
width="100%"
144+
x="0"
145+
y="196"
146+
/>
147+
<rect
148+
fill="transparent"
149+
height="100%"
150+
pointer-events="auto"
151+
width="calc(100vw - 251px)"
152+
x="251"
153+
y="0"
154+
/>
155+
</svg>
156+
</div>
157+
</div>
158+
</body>
159+
`;
160+
3161
exports[`Tour single 1`] = `
4162
<div
5163
style="margin: 20px;"

tests/index.test.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React, { useState, useRef } from 'react';
22
import { fireEvent, render, screen } from '@testing-library/react';
33
import Tour from '../src/index';
4+
import {spyElementPrototypes} from "rc-util/lib/test/domHook";
5+
import {act} from "react-dom/test-utils";
6+
import {resizeWindow} from "./utils";
47

58
describe('Tour', () => {
69
beforeEach(() => {
@@ -342,4 +345,77 @@ describe('Tour', () => {
342345
fireEvent.click(document.querySelector('.open-tour'));
343346
expect(document.querySelector('.rc-tour-title').innerHTML).toBe('step 2');
344347
});
348+
349+
const mockBtnRect = (rect: { x: number, y: number, width: number, height: number }) => {
350+
spyElementPrototypes(HTMLButtonElement, {
351+
getBoundingClientRect: {
352+
get(): any {
353+
return () => ({...rect, left: rect.x, top: rect.y});
354+
}
355+
},
356+
scrollIntoView: {
357+
get(): any {
358+
return (val) => val
359+
}
360+
}
361+
362+
});
363+
}
364+
365+
it('should update position when window resize', async () => {
366+
mockBtnRect({
367+
x: 800,
368+
y: 333,
369+
width: 230,
370+
height: 180
371+
});
372+
const Demo = () => {
373+
const btnRef = useRef<HTMLButtonElement>(null);
374+
375+
return (
376+
<div style={{width: '100%'}}>
377+
<button className="btn2" ref={btnRef} onClick={() => {
378+
mockBtnRect({
379+
x: 15,
380+
y: 10,
381+
width: 230,
382+
height: 180
383+
});
384+
resizeWindow(300, 200);
385+
}}>
386+
按钮
387+
</button>
388+
<Tour
389+
open
390+
placement={'bottom'}
391+
steps={[
392+
{
393+
title: '创建',
394+
description: '创建一条数据',
395+
target: () => btnRef.current,
396+
},
397+
]}
398+
/>
399+
</div>
400+
);
401+
};
402+
const {baseElement, unmount} = render(<Demo/>);
403+
expect(baseElement.querySelector('.rc-tour-target-placeholder')).toHaveStyle('left: 794px; top: 327px; width: 242px; height: 192px;');
404+
fireEvent.click(baseElement.querySelector('.btn2'));
405+
await act(() => {
406+
jest.runAllTimers();
407+
});
408+
expect(baseElement.querySelector('.rc-tour-target-placeholder')).toHaveStyle('left: 9px; top: 4px; width: 242px; height: 192px;');
409+
410+
expect(baseElement).toMatchSnapshot();
411+
412+
unmount();
413+
mockBtnRect({
414+
x: 0,
415+
y: 0,
416+
width: 0,
417+
height: 0
418+
});
419+
420+
});
345421
});

tests/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const resizeWindow = (x: number, y: number) => {
2+
// @ts-ignore
3+
window.innerWidth = x;
4+
// @ts-ignore
5+
window.innerHeight = y;
6+
window.dispatchEvent(new Event('resize'));
7+
};

0 commit comments

Comments
 (0)