Skip to content
This repository was archived by the owner on Nov 4, 2025. It is now read-only.

Commit 4f6ac65

Browse files
authored
feat: Add keepAlign support (#36)
* feat: Add keepAlign support * add test case
1 parent 21a8a99 commit 4f6ac65

File tree

3 files changed

+153
-7
lines changed

3 files changed

+153
-7
lines changed

examples/position.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { Component } from 'react';
2+
import Align from '../src';
3+
4+
const align = {
5+
points: ['cc', 'cc'],
6+
};
7+
8+
class Demo extends Component {
9+
state = {
10+
left: 0,
11+
top: 0,
12+
};
13+
14+
eleRef = React.createRef();
15+
16+
render() {
17+
const { left, top } = this.state;
18+
19+
return (
20+
<div style={{ marginBottom: 170 }}>
21+
<div
22+
style={{
23+
margin: 20,
24+
border: '1px solid red',
25+
padding: '100px 0',
26+
textAlign: 'center',
27+
position: 'relative',
28+
}}
29+
onClick={this.onClick}
30+
>
31+
<div
32+
ref={this.eleRef}
33+
style={{
34+
width: 10,
35+
height: 10,
36+
background: 'red',
37+
position: 'absolute',
38+
left,
39+
top,
40+
}}
41+
/>
42+
</div>
43+
44+
<Align
45+
ref={this.alignRef}
46+
target={() => this.eleRef.current}
47+
align={align}
48+
keepingAlign
49+
>
50+
<div
51+
style={{
52+
position: 'absolute',
53+
width: 100,
54+
height: 100,
55+
background: 'rgba(0, 255, 0, 0.5)',
56+
pointerEvents: 'none',
57+
}}
58+
>
59+
Align
60+
</div>
61+
</Align>
62+
63+
<button
64+
type="button"
65+
onClick={() => {
66+
this.setState({
67+
left: Math.random() * 500,
68+
top: Math.random() * 200,
69+
});
70+
}}
71+
>
72+
Random Position
73+
</button>
74+
</div>
75+
);
76+
}
77+
}
78+
79+
export default Demo;

src/Align.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface AlignProps {
2222
monitorWindowResize?: boolean;
2323
disabled?: boolean;
2424
children: React.ReactElement;
25+
/** Always trigger align with each render */
26+
keepAlign?: boolean;
2527
}
2628

2729
interface MonitorRef {
@@ -43,11 +45,27 @@ function getPoint(point: TargetType) {
4345
return point;
4446
}
4547

48+
interface InternalTestProps {
49+
INTERNAL_TRIGGER_ALIGN?: Function;
50+
}
51+
4652
const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
47-
{ children, disabled, target, align, onAlign, monitorWindowResize, monitorBufferTime = 0 },
53+
{
54+
children,
55+
disabled,
56+
target,
57+
align,
58+
onAlign,
59+
monitorWindowResize,
60+
monitorBufferTime = 0,
61+
keepAlign,
62+
...restProps
63+
},
4864
ref,
4965
) => {
50-
const cacheRef = React.useRef<{ element?: HTMLElement; point?: TargetPoint }>({});
66+
const cacheRef = React.useRef<{ element?: HTMLElement; point?: TargetPoint }>(
67+
{},
68+
);
5169
const nodeRef = React.useRef();
5270
let childNode = React.Children.only(children);
5371

@@ -63,7 +81,17 @@ const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
6381
forceAlignPropsRef.current.onAlign = onAlign;
6482

6583
const [forceAlign, cancelForceAlign] = useBuffer(() => {
66-
const { disabled: latestDisabled, target: latestTarget } = forceAlignPropsRef.current;
84+
if (
85+
process.env.NODE_ENV !== 'production' &&
86+
(restProps as InternalTestProps).INTERNAL_TRIGGER_ALIGN
87+
) {
88+
(restProps as InternalTestProps).INTERNAL_TRIGGER_ALIGN();
89+
}
90+
91+
const {
92+
disabled: latestDisabled,
93+
target: latestTarget,
94+
} = forceAlignPropsRef.current;
6795
if (!latestDisabled && latestTarget) {
6896
const source = nodeRef.current;
6997

@@ -112,10 +140,16 @@ const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
112140
if (nodeRef.current !== sourceResizeMonitor.current.element) {
113141
sourceResizeMonitor.current.cancel();
114142
sourceResizeMonitor.current.element = nodeRef.current;
115-
sourceResizeMonitor.current.cancel = monitorResize(nodeRef.current, forceAlign);
143+
sourceResizeMonitor.current.cancel = monitorResize(
144+
nodeRef.current,
145+
forceAlign,
146+
);
116147
}
117148

118-
if (cacheRef.current.element !== element || !isSamePoint(cacheRef.current.point, point)) {
149+
if (
150+
cacheRef.current.element !== element ||
151+
!isSamePoint(cacheRef.current.point, point)
152+
) {
119153
forceAlign();
120154

121155
// Add resize observer
@@ -136,6 +170,15 @@ const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
136170
}
137171
}, [disabled]);
138172

173+
/**
174+
* [Legacy] Should keep re-algin since we don't know if target position changed.
175+
*/
176+
React.useEffect(() => {
177+
if (keepAlign && !disabled) {
178+
forceAlign(true);
179+
}
180+
});
181+
139182
// Listen for window resize
140183
const winResizeRef = React.useRef<{ remove: Function }>(null);
141184
React.useEffect(() => {

tests/element.test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ describe('element align', () => {
2626
render() {
2727
return (
2828
<div style={{ paddingTop: 100 }}>
29-
<div ref={this.targetRef} style={{ display: 'inline-block', width: 50, height: 50 }}>
29+
<div
30+
ref={this.targetRef}
31+
style={{ display: 'inline-block', width: 50, height: 50 }}
32+
>
3033
target
3134
</div>
3235
<Align target={this.getTarget} align={align} {...this.props}>
@@ -73,12 +76,33 @@ describe('element align', () => {
7376
it('disabled should trigger align', () => {
7477
const onAlign = jest.fn();
7578

76-
const wrapper = mount(<Test monitorWindowResize onAlign={onAlign} disabled />);
79+
const wrapper = mount(
80+
<Test monitorWindowResize onAlign={onAlign} disabled />,
81+
);
7782
expect(onAlign).not.toHaveBeenCalled();
7883

7984
wrapper.setProps({ disabled: false });
8085
jest.runAllTimers();
8186
expect(onAlign).toHaveBeenCalled();
8287
});
88+
89+
it('keepAlign', () => {
90+
const triggerAlign = jest.fn();
91+
92+
class TestAlign extends React.Component {
93+
state = {};
94+
95+
render = () => <Test INTERNAL_TRIGGER_ALIGN={triggerAlign} keepAlign />;
96+
}
97+
98+
const wrapper = mount(<TestAlign />);
99+
const times = triggerAlign.mock.calls.length;
100+
101+
for (let i = 0; i < 10; i += 1) {
102+
wrapper.instance().forceUpdate();
103+
}
104+
105+
expect(triggerAlign.mock.calls.length > times).toBeTruthy();
106+
});
83107
});
84108
/* eslint-enable */

0 commit comments

Comments
 (0)