Skip to content

Commit 0e34327

Browse files
authored
Merge pull request #1 from Runjuu/master
init project
2 parents db411d1 + fa6dfdd commit 0e34327

13 files changed

+928
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
/dist
3+
/.idea

.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/node_modules
2+
/.idea
3+
/src
4+
tsconfig.json
5+
tslint.json
6+
yarn.lock

package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "react-resize-context",
3+
"version": "1.0.0",
4+
"description": "A high performance React component for responding to resize event",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"scripts": {
8+
"build": "rm -rf ./dist && tsc",
9+
"test": "echo \"Error: no test specified\" && exit 1"
10+
},
11+
"repository": {
12+
"type": "git",
13+
"url": "git+https://github.com/LeetCode-OpenSource/react-resize-context.git"
14+
},
15+
"keywords": [
16+
"react",
17+
"resize",
18+
"detector"
19+
],
20+
"bugs": {
21+
"url": "https://github.com/LeetCode-OpenSource/react-resize-context/issues"
22+
},
23+
"homepage": "https://github.com/LeetCode-OpenSource/react-resize-context#readme",
24+
"author": "LeetCode front-end team",
25+
"license": "MIT",
26+
"devDependencies": {
27+
"@types/element-resize-detector": "^1.1.0",
28+
"@types/lodash": "^4.14.115",
29+
"@types/prop-types": "^15.5.4",
30+
"@types/react": "^16.4.7",
31+
"@types/shallowequal": "^0.2.3",
32+
"tslib": "^1.9.3",
33+
"tslint": "^5.11.0",
34+
"tslint-eslint-rules": "^5.3.1",
35+
"tslint-react": "^3.6.0",
36+
"tslint-sonarts": "^1.7.0",
37+
"typescript": "^2.9.2"
38+
},
39+
"dependencies": {
40+
"element-resize-detector": "^1.1.14",
41+
"lodash": "^4.17.10",
42+
"shallowequal": "^1.1.0"
43+
},
44+
"peerDependencies": {
45+
"prop-types": "^15.6.2",
46+
"react": "^16.3.0"
47+
}
48+
}

src/Listener.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as elementResizeDetectorMaker from 'element-resize-detector';
2+
import * as shallowequal from 'shallowequal';
3+
import { once } from 'lodash';
4+
5+
import { getSize } from './methods';
6+
7+
import { OnResizeCallBack, Size } from './types';
8+
9+
const getSharedListener = once(() => new Listener());
10+
11+
export default class Listener {
12+
public static get shared(): Listener {
13+
return getSharedListener();
14+
}
15+
16+
private resizeDetector = elementResizeDetectorMaker({ strategy: 'scroll' });
17+
18+
private callbacks = new Map<HTMLElement, Set<OnResizeCallBack>>();
19+
20+
private prevSize?: Size;
21+
22+
public startListenTo(element: HTMLElement, func: OnResizeCallBack) {
23+
if (element instanceof Element && typeof func === 'function') {
24+
const currentElementCallbacks = this.callbacks.get(element);
25+
26+
if (currentElementCallbacks) {
27+
currentElementCallbacks.add(func);
28+
} else {
29+
this.callbacks.set(element, new Set<OnResizeCallBack>([func]));
30+
}
31+
32+
func(getSize(element));
33+
}
34+
}
35+
36+
public stopListenTo(element: HTMLElement, func: OnResizeCallBack) {
37+
const currentElementCallbacks = this.callbacks.get(element);
38+
39+
if (currentElementCallbacks) {
40+
currentElementCallbacks.delete(func);
41+
}
42+
}
43+
44+
public startListen(element: HTMLElement) {
45+
this.resizeDetector.listenTo(element, this.onSizeChanged);
46+
}
47+
48+
public stopListen(element: HTMLElement) {
49+
this.resizeDetector.uninstall(element);
50+
}
51+
52+
private onSizeChanged = (element: HTMLElement) => {
53+
const callbacks = this.callbacks.get(element);
54+
55+
if (callbacks) {
56+
const size = getSize(element);
57+
58+
if (!shallowequal(this.prevSize, size)) {
59+
this.prevSize = size;
60+
callbacks.forEach((func) => func(size));
61+
}
62+
}
63+
};
64+
}

src/ResizeConsumer.tsx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import * as PropTypes from 'prop-types';
2+
import * as React from 'react';
3+
import { omit } from 'lodash';
4+
5+
import { HTMLAttributes, RefObject } from 'react';
6+
7+
import { Consumer, ContextValue } from './context';
8+
import Listener from './Listener';
9+
import { isRefObject, updateElementDataAttributes } from './methods';
10+
11+
import { Size } from './types';
12+
13+
interface ForwardRefProps extends HTMLAttributes<HTMLDivElement> {
14+
onSizeChanged?: (size: Size) => void;
15+
updateDatasetBySize?: (size: Size) => DOMStringMap;
16+
}
17+
18+
interface Props extends ForwardRefProps {
19+
context: ContextValue;
20+
forwardedRef: RefObject<HTMLDivElement>;
21+
}
22+
23+
class ResizeConsumer extends React.PureComponent<Props> {
24+
private currentListenElement: HTMLElement | null = null;
25+
26+
private get divProps() {
27+
return omit(this.props, [
28+
'context',
29+
'forwardedRef',
30+
'onSizeChanged',
31+
'updateDatasetBySize',
32+
]);
33+
}
34+
35+
public componentDidMount() {
36+
this.updateListener();
37+
}
38+
39+
public componentDidUpdate() {
40+
this.updateListener();
41+
}
42+
43+
public componentWillUnmount() {
44+
this.stopListen();
45+
}
46+
47+
public render() {
48+
return (
49+
<div {...this.divProps} ref={this.props.forwardedRef}>
50+
{this.props.children}
51+
</div>
52+
);
53+
}
54+
55+
private updateAttribute = (size: Size) => {
56+
const element = this.props.forwardedRef.current;
57+
if (element && typeof this.props.updateDatasetBySize === 'function') {
58+
const newDataAttributes = this.props.updateDatasetBySize(size);
59+
updateElementDataAttributes(element, newDataAttributes);
60+
}
61+
};
62+
63+
private onSizeChanged = (size: Size) => {
64+
if (typeof this.props.onSizeChanged === 'function') {
65+
this.props.onSizeChanged(size);
66+
}
67+
this.updateAttribute(size);
68+
};
69+
70+
private updateListener(
71+
nextListenElement: HTMLElement | null = this.props.context.listenElement,
72+
) {
73+
if (this.currentListenElement !== nextListenElement) {
74+
this.startListenTo(nextListenElement);
75+
}
76+
}
77+
78+
private startListenTo(element: HTMLElement | null) {
79+
this.stopListen();
80+
81+
if (element) {
82+
Listener.shared.startListenTo(element, this.onSizeChanged);
83+
this.currentListenElement = element;
84+
}
85+
}
86+
87+
private stopListen() {
88+
if (this.currentListenElement) {
89+
Listener.shared.stopListenTo(
90+
this.currentListenElement,
91+
this.onSizeChanged,
92+
);
93+
}
94+
}
95+
}
96+
97+
const ForwardRef = React.forwardRef<HTMLDivElement, ForwardRefProps>(
98+
(props, ref) => (
99+
<Consumer>
100+
{(context) => (
101+
<ResizeConsumer
102+
{...props}
103+
context={context}
104+
forwardedRef={
105+
isRefObject(ref)
106+
? (ref as RefObject<HTMLDivElement>)
107+
: React.createRef()
108+
}
109+
/>
110+
)}
111+
</Consumer>
112+
),
113+
);
114+
115+
ForwardRef.propTypes = {
116+
onSizeChanged: PropTypes.func,
117+
updateDatasetBySize: PropTypes.func,
118+
};
119+
120+
ForwardRef.defaultProps = {
121+
onSizeChanged: undefined,
122+
updateDatasetBySize: undefined,
123+
};
124+
125+
export default ForwardRef;

src/ResizeProvider.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as React from 'react';
2+
3+
import { Provider } from './context';
4+
import Listener from './Listener';
5+
import { getElement, makeSureChildrenHasRef } from './methods';
6+
7+
import { ResizeChildren } from './types';
8+
import { ReactNode } from 'react';
9+
10+
interface Props {
11+
children?: ReactNode;
12+
}
13+
14+
interface State {
15+
children: ResizeChildren | null;
16+
element: HTMLElement | null;
17+
}
18+
19+
export default class ResizeProvider extends React.PureComponent<Props, State> {
20+
public static getDerivedStateFromProps(props: Props) {
21+
return {
22+
children: makeSureChildrenHasRef(props.children),
23+
};
24+
}
25+
26+
public state: State = {
27+
children: null,
28+
element: null,
29+
};
30+
31+
public componentDidMount() {
32+
this.updateListenElement();
33+
}
34+
35+
public componentDidUpdate() {
36+
this.updateListenElement();
37+
}
38+
39+
public componentWillUnmount() {
40+
this.removeListenElement();
41+
}
42+
43+
public render() {
44+
return (
45+
<Provider value={{ listenElement: this.state.element }}>
46+
{this.state.children}
47+
</Provider>
48+
);
49+
}
50+
51+
private updateListenElement() {
52+
const element = getElement(this.state.children);
53+
54+
if (element !== this.state.element) {
55+
this.removeListenElement();
56+
57+
if (element) {
58+
Listener.shared.startListen(element);
59+
}
60+
61+
this.setState({ element });
62+
}
63+
}
64+
65+
private removeListenElement() {
66+
if (this.state.element) {
67+
Listener.shared.stopListen(this.state.element);
68+
}
69+
}
70+
}

src/context.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as React from 'react';
2+
3+
export interface ContextValue {
4+
listenElement: HTMLElement | null;
5+
}
6+
7+
export const { Provider, Consumer } = React.createContext<ContextValue>({
8+
listenElement: null,
9+
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as ResizeProvider } from './ResizeProvider';
2+
export { default as ResizeConsumer } from './ResizeConsumer';

0 commit comments

Comments
 (0)