react-beautiful-dnd
Version:
Beautiful, accessible drag and drop for lists with React.js
135 lines (110 loc) • 3.83 kB
Flow
// @flow
import { Component } from 'react';
import type { Node } from 'react';
import PropTypes from 'prop-types';
import memoizeOne from 'memoize-one';
import getWindowScrollPosition from '../get-window-scroll-position';
import { getDraggableDimension } from '../../state/dimension';
import { dimensionMarshalKey } from '../context-keys';
import getArea from '../../state/get-area';
import type {
DraggableDescriptor,
DraggableDimension,
Spacing,
Area,
DraggableId,
DroppableId,
} from '../../types';
import type { DimensionMarshal } from '../../state/dimension-marshal/dimension-marshal-types';
type Props = {|
// (it is being used)
/* eslint-disable react/no-unused-prop-types */
draggableId: DraggableId,
droppableId: DroppableId,
index: number,
/* eslint-enable react/no-unused-prop-types */
targetRef: ?HTMLElement,
children: Node,
|}
export default class DraggableDimensionPublisher extends Component<Props> {
/* eslint-disable react/sort-comp */
static contextTypes = {
[dimensionMarshalKey]: PropTypes.object.isRequired,
};
publishedDescriptor: ?DraggableDescriptor = null
getDescriptor(props: Props): DraggableDescriptor {
const { draggableId, droppableId, index } = props;
const descriptor: DraggableDescriptor = this.getMemoizedDescriptor(
draggableId, droppableId, index
);
return descriptor;
}
componentWillMount() {
this.publish(this.getDescriptor(this.props));
}
componentWillReceiveProps(nextProps: Props) {
this.publish(this.getDescriptor(nextProps));
}
componentWillUnmount() {
this.unpublish();
}
getMemoizedDescriptor = memoizeOne(
(id: DraggableId, droppableId: DroppableId, index: number): DraggableDescriptor => ({
id,
droppableId,
index,
}));
unpublish = () => {
if (!this.publishedDescriptor) {
console.error('cannot unpublish descriptor when none is published');
return;
}
// Using the previously published id to unpublish. This is to guard
// against the case where the id dynamically changes. This is not
// supported during a drag - but it is good to guard against.
const marshal: DimensionMarshal = this.context[dimensionMarshalKey];
marshal.unregisterDraggable(this.publishedDescriptor);
this.publishedDescriptor = null;
}
publish = (descriptor: DraggableDescriptor) => {
if (descriptor === this.publishedDescriptor) {
return;
}
if (this.publishedDescriptor) {
this.unpublish();
}
const marshal: DimensionMarshal = this.context[dimensionMarshalKey];
marshal.registerDraggable(descriptor, this.getDimension);
this.publishedDescriptor = descriptor;
}
getDimension = (): DraggableDimension => {
const targetRef: ?HTMLElement = this.props.targetRef;
if (!targetRef) {
throw new Error('DraggableDimensionPublisher cannot calculate a dimension when not attached to the DOM');
}
const descriptor: ?DraggableDescriptor = this.publishedDescriptor;
if (!descriptor) {
throw new Error('Cannot get dimension for unpublished draggable');
}
const style = window.getComputedStyle(targetRef);
const margin: Spacing = {
top: parseInt(style.marginTop, 10),
right: parseInt(style.marginRight, 10),
bottom: parseInt(style.marginBottom, 10),
left: parseInt(style.marginLeft, 10),
};
// We do not need to worry about 'box-sizing' because getBoundingClientRect already
// takes that into account
const client: Area = getArea(targetRef.getBoundingClientRect());
const dimension: DraggableDimension = getDraggableDimension({
descriptor,
client,
margin,
windowScroll: getWindowScrollPosition(),
});
return dimension;
}
render() {
return this.props.children;
}
}