Skip to content

Instantly share code, notes, and snippets.

@AlexFrazer
Created April 13, 2018 02:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save AlexFrazer/aed3810407aaf23b23168449a7ef83bf to your computer and use it in GitHub Desktop.
Save AlexFrazer/aed3810407aaf23b23168449a7ef83bf to your computer and use it in GitHub Desktop.
import * as React from 'react';
export interface Props {
selectableKey: string;
children?: React.ReactNode;
}
export interface ComponentProps {
selectableRef: React.Ref<HTMLElement>;
}
export default function createSelectable<TProps>(
Wrapped: React.ComponentType<ComponentProps & TProps>
) {
return class SelectableItem extends React.PureComponent<Props & TProps> {
static contextTypes = {
selectable: PropTypes.shape({
insert: PropTypes.func,
remove: PropTypes.func,
}),
};
onRef: React.Ref<HTMLElement> = (element) => {
if (element) {
this.context.selectable.insert(this.props.selectableKey, element);
} else {
this.context.selectable.remove(this.props.selectableKey, element);
}
}
render() {
return (
<WrappedComponent
{...this.props}
selectableRef={this}
/>
);
}
}
}
import * as React from 'react';
import * as rbush from 'rbush';
import * as PropTypes from 'prop-types';
export interface Props {
/* +/- how many pixels from mousedown */
tolerance: number;
/* What to do on selection */
onSelection(o: { selection: Set<string>, e: React.MouseEvent<HTMLDivElement> }): void;
/* What to do when selection has been completed */
onSelectionDone: React.MouseEventHandler<HTMLDivElement>;
/* what to do after starting selection */
onSelectionStart: React.MouseEventHandler<HTMLDivElement>;
}
export interface State {
minX: number;
maxX: number;
minY: number;
maxY: number;
}
export interface SelectableNode extends rbush.BBox {
key: string; // will be used to reference the node.
}
export default class SelectableGroup extends React.PureComponent<Props, State> {
static defaultProps = {
tolerance: 10,
};
static childContextTypes = {
selectable: PropTypes.shape({
insert: PropTypes.func,
remove: PropTypes.func,
})
}
getChildContext() {
return {
selectable: {
insert: this.insert,
remove: this.remove,
}
}
}
initialX: number = 0;
initialY: number = 0;
tree: rbush.RBush<SelectableNode>();
nodes: Map<string, HTMLElement> = new Map();
insert = (key: string, node: HTMLElement) => {
this.nodes.set(key, node);
this.tree.insert(createTreeNode(node));
}
remove = (key: string, node: HTMLElement) => {
this.nodes.delete(key);
this.tree.remove(createTreeNode(node), ({ key: a }, { key: b }) => a === b);
}
updateNode = (key: string, node: HTMLElement) => {
this.removeNode(node);
this.insertNode(node);
};
onMouseDown: React.MouseEventHandler<HTMLDivElement> = ({ pageX, pageY }) => {
this.initialX = pageX;
this.initialY = pageY;
this.props.onSelectionStart();
window.addEventListener('mouseup', this.onMouseUp);
window.addEventListener('mousemove', this.onMouseMove);
}
onMouseMove: MouseEventHandler = ({ pageX, pageY }) => {
this.setState({
minX: Math.min(this.initialX, pageX),
maxX: Math.max(this.initialX, pageX),
minY: Math.min(this.initialY, pageY),
maxY: Math.max(this.initialY, pageY),
}, () => {
const collisions = this.tree.search(this.state);
this.props.onSelection(new Set(collisions));
})
}
onMouseUp: MouseEventHandler = () => {
this.props.onSelectionDone();
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('mouseup', this.onMouseUp);
}
render() {
return (
<Container
{...props}
onMouseDown={this.onMouseDown}
>
{children}
</Container>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment