Skip to content
Open
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
7 changes: 4 additions & 3 deletions Example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

buildscript {
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath("com.android.tools.build:gradle:4.2.2")

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand All @@ -15,7 +16,7 @@ buildscript {
allprojects {
repositories {
mavenLocal()
jcenter()
mavenCentral()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
Expand Down
137 changes: 69 additions & 68 deletions lib/Popover.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,108 @@
import React, { PureComponent } from 'react';
import { findNodeHandle, Text, ViewPropTypes } from 'react-native';
import { findNodeHandle } from 'react-native';
import PropTypes from 'prop-types';
import { v4 as uuidv4 } from 'uuid';

class Popover extends PureComponent {
static contextTypes = {
registerPopover: PropTypes.func,
unregisterPopover: PropTypes.func,
registerPopover: PropTypes.func,
unregisterPopover: PropTypes.func,
};

static propTypes = {
children: PropTypes.node,
isVisible: PropTypes.bool,
arrowColor: PropTypes.string,
arrowWidth: PropTypes.number,
arrowHeight: PropTypes.number,
placement: PropTypes.oneOf(['left', 'right', 'top', 'bottom', 'auto']),
pointerEvents: PropTypes.string,
offset: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}),
children: PropTypes.node,
isVisible: PropTypes.bool,
arrowColor: PropTypes.string,
arrowWidth: PropTypes.number,
arrowHeight: PropTypes.number,
placement: PropTypes.oneOf(['left', 'right', 'top', 'bottom', 'auto']),
pointerEvents: PropTypes.string,
offset: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}),
};

static defaultProps = {
children: null,
isVisible: true,
arrowColor: 'white',
arrowWidth: 15,
arrowHeight: 10,
placement: 'auto',
pointerEvents: 'box-none',
offset: {
x: 0,
y: 0,
},
children: null,
isVisible: true,
arrowColor: 'white',
arrowWidth: 15,
arrowHeight: 10,
placement: 'auto',
pointerEvents: 'box-none',
offset: {
x: 0,
y: 0,
},
};

constructor(props) {
super(props);
this._id = uuidv4();
super(props);
this._id = uuidv4();
}

componentDidMount() {
if (!this.props.isVisible) {
return;
}
this.registerSelf();
if (!this.props.isVisible) {
return;
}
this.registerSelf();
}

componentDidUpdate(prevProps) {
if (prevProps.isVisible !== this.props.isVisible || prevProps.placement !== this.props.placement) {
if (this.props.isVisible) {
this.registerSelf();
} else {
this.unregisterSelf();
if (prevProps.isVisible !== this.props.isVisible || prevProps.placement !== this.props.placement) {
if (this.props.isVisible) {
this.registerSelf();
} else {
this.unregisterSelf();
}
}
}
}

componentWillUnmount() {
this.unregisterSelf();
this.unregisterSelf();
}

setElementRef = x => {
this._element = x;
setElementRef = (x) => {
this._element = x;
};

registerSelf() {
// delay to the next tick to guarantee layout
setTimeout(() => {
if (this._element !== null) {
const {
arrowColor,
arrowWidth,
arrowHeight,
placement,
component,
pointerEvents,
offset,
} = this.props;
this.context.registerPopover(this._id, findNodeHandle(this._element), {
arrowColor,
arrowWidth,
arrowHeight,
placement,
component,
pointerEvents,
offset,
});
}
});
// delay to the next tick to guarantee layout
setTimeout(() => {
if (this._element !== null) {
const {
arrowColor,
arrowWidth,
arrowHeight,
placement,
component,
pointerEvents,
offset,
} = this.props;
this.context.registerPopover(this._id, findNodeHandle(this._element), {
arrowColor,
arrowWidth,
arrowHeight,
placement,
component,
pointerEvents,
offset,
});
}
});
}

unregisterSelf() {
this.context.unregisterPopover(this._id);
this.context.unregisterPopover(this._id);
}

render() {
const child = React.Children.only(this.props.children);
return React.cloneElement(child, {
ref: this.setElementRef,
});
const child = React.Children.only(this.props.children);
return React.cloneElement(child, {
ref: this.setElementRef,
collapsable: false, // to avoid crashes on Android
});
}
}

Expand Down
132 changes: 74 additions & 58 deletions lib/PopoverContainer.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,111 @@
import React, { Component } from 'react';
import { PanResponder, findNodeHandle, UIManager, View } from 'react-native';
import { UIManager, View } from 'react-native';
import PropTypes from 'prop-types';
import PopoverElement from './PopoverElement';

const addKey = (obj, key, value) => ({ ...obj, [key]: value });

const delKey = (obj, key) => {
const copy = Object.assign({}, obj);
delete copy[key];
return copy;
const copy = Object.assign({}, obj);
delete copy[key];
return copy;
};

class PopoverContainer extends Component {
static propTypes = {
children: PropTypes.node,
padding: PropTypes.number,
children: PropTypes.node,
padding: PropTypes.number,
};

static defaultProps = {
children: null,
padding: 0,
children: null,
padding: 0,
};

static childContextTypes = {
registerPopover: PropTypes.func,
unregisterPopover: PropTypes.func,
registerPopover: PropTypes.func,
unregisterPopover: PropTypes.func,
};

state = {
registry: {},
containerSize: null,
registry: {},
containerSize: null,
};

getChildContext() {
return {
registerPopover: this.registerPopover,
unregisterPopover: this.unregisterPopover,
};
return {
registerPopover: this.registerPopover,
unregisterPopover: this.unregisterPopover,
};
}

onRootLayout = ({ nativeEvent: { layout: { width, height } } }) => {
this.setState({ containerSize: { width, height } });
this.setState({ containerSize: { width, height } });
};

registerPopover = (id, element, props) => {
if (this.state.containerSize === null) {
setTimeout(() => this.registerPopover(id, element, props));
return;
}
UIManager.measureLayout(
element,
findNodeHandle(this._root),
err => {
console.error(err);
},
(x, y, width, height) => {
this.setState({
registry: addKey(this.state.registry, id, {
rect: { x, y, width, height },
props,
}),
});
},
);
if (this.state.containerSize === null) {
setTimeout(() => this.registerPopover(id, element, props));
return;
}
UIManager.measureInWindow(
element,
(x, y, width, height) => {
this.setState({
registry: addKey(this.state.registry, id, {
rect: {
x, y, width, height,
},
props,
}),
});
},
);
/* TODO: investigate why the code bellow doesn't work
UIManager.measureLayout(
element,
findNodeHandle(this._root),
(err) => {
console.error(err);
},
(x, y, width, height) => {
this.setState({
registry: addKey(this.state.registry, id, {
rect: {
x, y, width, height,
},
props,
}),
});
},
);
*/
};

unregisterPopover = id => {
this.setState({ registry: delKey(this.state.registry, id) });
unregisterPopover = (id) => {
this.setState(state => ({ registry: delKey(state.registry, id) }));
};

render() {
return (
<View
ref={x => {
this._root = x;
}}
style={{ flex: 1 }}
onLayout={this.onRootLayout}
>
{this.props.children}
{Object.entries(this.state.registry).map(([id, { rect, props }]) =>
<PopoverElement
key={id}
containerSize={this.state.containerSize}
padding={this.props.padding}
fromRect={rect}
{...props}
/>,
)}
</View>
);
return (
<View
ref={(x) => {
this._root = x;
}}
style={{ flex: 1 }}
onLayout={this.onRootLayout}
>
{this.props.children}
{Object.entries(this.state.registry).map(([id, { rect, props }]) =>
(<PopoverElement
key={id}
containerSize={this.state.containerSize}
padding={this.props.padding}
fromRect={rect}
{...props}
/>))}
</View>
);
}
}

Expand Down
6 changes: 4 additions & 2 deletions lib/PopoverElement.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { Component } from 'react';
import { StyleSheet, View, ViewPropTypes } from 'react-native';
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import computeGeometry from './computeGeometry';
import {
PLACEMENT_OPTIONS,
capitalizeFirstLetter,
findDirectionWithoutColor,
} from './utils';
Expand Down Expand Up @@ -95,6 +94,9 @@ class PopoverElement extends Component {
}

measureLayout = ({ nativeEvent: { layout: { width, height } } }) => {
if (width === this.state.width && height === this.state.height) {
return;
}
this.setState({ width, height }, () => this.computeStyles(this.props));
};

Expand Down