Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit 9201c9b

Browse files
maclockardFezVrasta
authored andcommitted
Allow ref objects to be passed (#308)
* Allow ref objects to be passed * fix tests * Added tests
1 parent ac37373 commit 9201c9b

File tree

9 files changed

+256
-60
lines changed

9 files changed

+256
-60
lines changed

.size-snapshot.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"dist/index.umd.js": {
3-
"bundled": 66802,
4-
"minified": 23724,
5-
"gzipped": 7169
3+
"bundled": 67132,
4+
"minified": 23802,
5+
"gzipped": 7191
66
},
77
"dist/index.umd.min.js": {
8-
"bundled": 31438,
9-
"minified": 12846,
10-
"gzipped": 4182
8+
"bundled": 31768,
9+
"minified": 12924,
10+
"gzipped": 4205
1111
},
1212
"dist/index.esm.js": {
13-
"bundled": 12271,
14-
"minified": 7288,
15-
"gzipped": 2066,
13+
"bundled": 12577,
14+
"minified": 7364,
15+
"gzipped": 2096,
1616
"treeshaked": {
1717
"rollup": {
18-
"code": 3754,
18+
"code": 3832,
1919
"import_statements": 137
2020
},
2121
"webpack": {
22-
"code": 4860
22+
"code": 4938
2323
}
2424
}
2525
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@
8484
"babel-jest": "^24.0.0",
8585
"cross-env": "^5.1.4",
8686
"emotion": "^9.1.1",
87-
"enzyme": "^3.3.0",
88-
"enzyme-adapter-react-16": "^1.1.1",
89-
"enzyme-to-json": "^3.3.3",
87+
"enzyme": "^3.10.0",
88+
"enzyme-adapter-react-16": "^1.14.0",
89+
"enzyme-to-json": "^3.4.0",
9090
"eslint": "^4.19.1",
9191
"eslint-config-prettier": "^2.9.0",
9292
"eslint-plugin-flowtype": "^2.46.1",

src/Popper.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ import PopperJS, {
99
} from 'popper.js';
1010
import type { Style } from 'typed-styles';
1111
import { ManagerContext } from './Manager';
12-
import { safeInvoke, unwrapArray, shallowEqual } from './utils';
12+
import { unwrapArray, setRef, shallowEqual } from './utils';
13+
import { type Ref } from "./RefTypes";
1314

14-
type getRefFn = (?HTMLElement) => void;
1515
type ReferenceElement = ReferenceObject | HTMLElement | null;
1616
type StyleOffsets = { top: number, left: number };
1717
type StylePosition = { position: 'absolute' | 'fixed' };
1818

1919
export type PopperArrowProps = {
20-
ref: getRefFn,
20+
ref: Ref,
2121
style: StyleOffsets & Style,
2222
};
2323
export type PopperChildrenProps = {|
24-
ref: getRefFn,
24+
ref: Ref,
2525
style: StyleOffsets & StylePosition & Style,
2626
placement: Placement,
2727
outOfBoundaries: ?boolean,
@@ -33,7 +33,7 @@ export type PopperChildren = PopperChildrenProps => React.Node;
3333
export type PopperProps = {
3434
children: PopperChildren,
3535
eventsEnabled?: boolean,
36-
innerRef?: getRefFn,
36+
innerRef?: Ref,
3737
modifiers?: Modifiers,
3838
placement?: Placement,
3939
positionFixed?: boolean,
@@ -76,7 +76,7 @@ export class InnerPopper extends React.Component<PopperProps, PopperState> {
7676
setPopperNode = (popperNode: ?HTMLElement) => {
7777
if (!popperNode || this.popperNode === popperNode) return;
7878

79-
safeInvoke(this.props.innerRef, popperNode);
79+
setRef(this.props.innerRef, popperNode);
8080
this.popperNode = popperNode;
8181

8282
this.updatePopperInstance();
@@ -200,7 +200,7 @@ export class InnerPopper extends React.Component<PopperProps, PopperState> {
200200
}
201201

202202
componentWillUnmount() {
203-
safeInvoke(this.props.innerRef, null);
203+
setRef(this.props.innerRef, null)
204204
this.destroyPopperInstance();
205205
}
206206

src/Popper.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,40 @@ describe('Popper component', () => {
8686
).not.toThrow();
8787
});
8888

89+
it('accepts a ref function', () => {
90+
const myRef = jest.fn();
91+
const referenceElement = document.createElement('div');
92+
mount(
93+
<InnerPopper referenceElement={referenceElement} innerRef={myRef}>
94+
{({ ref, style, placement}) => (
95+
<div
96+
ref={ref}
97+
style={style}
98+
data-placement={placement}
99+
/>
100+
)}
101+
</InnerPopper>
102+
)
103+
expect(myRef).toBeCalled();
104+
});
105+
106+
it('accepts a ref object', () => {
107+
const myRef = (React: any).createRef();
108+
const referenceElement = document.createElement('div');
109+
mount(
110+
<InnerPopper referenceElement={referenceElement} innerRef={myRef}>
111+
{({ ref, style, placement}) => (
112+
<div
113+
ref={ref}
114+
style={style}
115+
data-placement={placement}
116+
/>
117+
)}
118+
</InnerPopper>
119+
)
120+
expect(myRef.current).toBeDefined();
121+
});
122+
89123
it('accepts a `referenceElement` property', () => {
90124
class VirtualReference {
91125
getBoundingClientRect() {

src/RefTypes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type RefHandler = (?HTMLElement) => void;
2+
type RefObject = { current?: HTMLElement};
3+
4+
export type Ref = RefHandler | RefObject;

src/Reference.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
import * as React from 'react';
33
import warning from 'warning';
44
import { ManagerContext } from './Manager';
5-
import { safeInvoke, unwrapArray } from './utils';
5+
import { safeInvoke, unwrapArray, setRef } from './utils';
6+
import { type Ref } from "./RefTypes";
67

7-
export type ReferenceChildrenProps = { ref: (?HTMLElement) => void };
8+
export type ReferenceChildrenProps = { ref: Ref };
89
export type ReferenceProps = {
910
children: ReferenceChildrenProps => React.Node,
10-
innerRef?: (?HTMLElement) => void,
11+
innerRef?: Ref,
1112
};
1213

1314
type InnerReferenceProps = {
@@ -18,12 +19,12 @@ class InnerReference extends React.Component<
1819
ReferenceProps & InnerReferenceProps
1920
> {
2021
refHandler = (node: ?HTMLElement) => {
21-
safeInvoke(this.props.innerRef, node);
22+
setRef(this.props.innerRef, node)
2223
safeInvoke(this.props.setReferenceNode, node);
2324
};
2425

2526
componentWillUnmount() {
26-
safeInvoke(this.props.innerRef, null);
27+
setRef(this.props.innerRef, null)
2728
}
2829

2930
render() {

src/utils.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @flow
22

3+
import { type Ref } from "./RefTypes";
4+
35
/**
46
* Takes an argument and if it's an array, returns the first item in the array,
57
* otherwise returns the argument. Used for Preact compatibility.
@@ -38,3 +40,17 @@ export const shallowEqual = (objA: { [key: string]: any}, objB: { [key: string]:
3840

3941
return true;
4042
}
43+
44+
/**
45+
* Sets a ref using either a ref callback or a ref object
46+
*/
47+
export const setRef = (ref: ?Ref, node: ?HTMLElement) => {
48+
// if its a function call it
49+
if (typeof ref === "function") {
50+
return safeInvoke(ref, node);
51+
}
52+
// otherwise we should treat it as a ref object
53+
else if (ref != null) {
54+
ref.current = node;
55+
}
56+
}

typings/react-popper.d.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,37 @@ interface ManagerProps {
66
}
77
export class Manager extends React.Component<ManagerProps, {}> { }
88

9-
type RefHandler = (ref: HTMLElement | SVGElement | null) => void;
10-
119
interface ReferenceChildrenProps {
12-
ref: RefHandler;
10+
// React refs are supposed to be contravariant (allows a more general type to be passed rather than a more specific one)
11+
// However, Typescript currently can't infer that fact for refs
12+
// See https://github.com/microsoft/TypeScript/issues/30748 for more information
13+
ref: React.Ref<any>;
1314
}
1415

1516
interface ReferenceProps {
1617
children: (props: ReferenceChildrenProps) => React.ReactNode;
17-
innerRef?: RefHandler;
18+
innerRef?: React.Ref<any>;
1819
}
1920
export class Reference extends React.Component<ReferenceProps, {}> { }
2021

2122
export interface PopperArrowProps {
22-
ref: RefHandler;
23+
ref: React.Ref<any>;
2324
style: React.CSSProperties;
2425
}
2526

2627
export interface PopperChildrenProps {
2728
arrowProps: PopperArrowProps;
2829
outOfBoundaries: boolean | null;
2930
placement: PopperJS.Placement;
30-
ref: RefHandler;
31+
ref: React.Ref<any>;
3132
scheduleUpdate: () => void;
3233
style: React.CSSProperties;
3334
}
3435

3536
export interface PopperProps {
3637
children: (props: PopperChildrenProps) => React.ReactNode;
3738
eventsEnabled?: boolean;
38-
innerRef?: RefHandler;
39+
innerRef?: React.Ref<any>;
3940
modifiers?: PopperJS.Modifiers;
4041
placement?: PopperJS.Placement;
4142
positionFixed?: boolean;

0 commit comments

Comments
 (0)