react-beautiful-dnd
Version:
Beautiful, accessible drag and drop for lists with React.js
146 lines (124 loc) • 3.66 kB
JSX
// @flow
import {
calculateBox,
withScroll,
type BoxModel,
type Position,
} from 'css-box-model';
import memoizeOne from 'memoize-one';
import PropTypes from 'prop-types';
import { Component, type Node } from 'react';
import invariant from 'tiny-invariant';
import { dimensionMarshalKey } from '../context-keys';
import type {
DraggableDescriptor,
DraggableDimension,
Placeholder,
DraggableId,
DroppableId,
TypeId,
} from '../../types';
import type { DimensionMarshal } from '../../state/dimension-marshal/dimension-marshal-types';
type Props = {|
draggableId: DraggableId,
droppableId: DroppableId,
type: TypeId,
index: number,
getDraggableRef: () => ?HTMLElement,
children: Node,
|};
export default class DraggableDimensionPublisher extends Component<Props> {
/* eslint-disable react/sort-comp */
static contextTypes = {
[dimensionMarshalKey]: PropTypes.object.isRequired,
};
publishedDescriptor: ?DraggableDescriptor = null;
componentDidMount() {
this.publish();
}
componentDidUpdate() {
this.publish();
}
componentWillUnmount() {
this.unpublish();
}
getMemoizedDescriptor = memoizeOne(
(
id: DraggableId,
index: number,
droppableId: DroppableId,
type: TypeId,
): DraggableDescriptor => ({
id,
index,
droppableId,
type,
}),
);
publish = () => {
const marshal: DimensionMarshal = this.context[dimensionMarshalKey];
const descriptor: DraggableDescriptor = this.getMemoizedDescriptor(
this.props.draggableId,
this.props.index,
this.props.droppableId,
this.props.type,
);
if (!this.publishedDescriptor) {
marshal.registerDraggable(descriptor, this.getDimension);
this.publishedDescriptor = descriptor;
return;
}
// No changes to the descriptor
if (descriptor === this.publishedDescriptor) {
return;
}
marshal.updateDraggable(
this.publishedDescriptor,
descriptor,
this.getDimension,
);
this.publishedDescriptor = descriptor;
};
unpublish = () => {
invariant(
this.publishedDescriptor,
'Cannot unpublish descriptor when none is published',
);
// 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;
};
getDimension = (windowScroll: Position): DraggableDimension => {
const targetRef: ?HTMLElement = this.props.getDraggableRef();
const descriptor: ?DraggableDescriptor = this.publishedDescriptor;
invariant(
targetRef,
'DraggableDimensionPublisher cannot calculate a dimension when not attached to the DOM',
);
invariant(descriptor, 'Cannot get dimension for unpublished draggable');
const computedStyles: CSSStyleDeclaration = window.getComputedStyle(
targetRef,
);
const borderBox: ClientRect = targetRef.getBoundingClientRect();
const client: BoxModel = calculateBox(borderBox, computedStyles);
const page: BoxModel = withScroll(client, windowScroll);
const placeholder: Placeholder = {
client,
tagName: targetRef.tagName.toLowerCase(),
display: computedStyles.display,
};
const dimension: DraggableDimension = {
descriptor,
placeholder,
client,
page,
};
return dimension;
};
render() {
return this.props.children;
}
}