UNPKG

react-components

Version:

React components used by Khan Academy

174 lines (160 loc) 5.74 kB
const React = require('react'); const ReactDOM = require("react-dom"); const PT = React.PropTypes; // Takes an array of components to sort const SortableArea = React.createClass({displayName: "SortableArea", propTypes: { className: PT.string, components: PT.arrayOf(PT.node).isRequired, onReorder: PT.func.isRequired, style: PT.any, verify: PT.func, }, getDefaultProps: function() { return {verify: function() {return true;}}; }, getInitialState: function() { return { // index of the component being dragged dragging: null, components: this.props.components, }; }, // Firefox refuses to drag an element unless you set data on it. Hackily // add data each time an item is dragged. componentDidMount: function() { this._setDragEvents(); }, componentWillReceiveProps: function(nextProps) { this.setState({components: nextProps.components}); }, componentDidUpdate: function() { this._setDragEvents(); }, // Alternatively send each handler to each component individually, // partially applied onDragStart: function(startIndex) { this.setState({dragging: startIndex}); }, onDrop: function() { // tell the parent component this.setState({dragging: null}); this.props.onReorder(this.state.components); }, onDragEnter: function(enterIndex) { // When a label is first dragged it triggers a dragEnter with itself, // which we don't care about. if (this.state.dragging === enterIndex) { return; } const newComponents = this.state.components.slice(); // splice the tab out of its old position const removed = newComponents.splice(this.state.dragging, 1); // ... and into its new position newComponents.splice(enterIndex, 0, removed[0]); const verified = this.props.verify(newComponents); if (verified) { this.setState({ dragging: enterIndex, components: newComponents, }); } return verified; }, _listenEvent: function(e) { e.dataTransfer.setData('hackhackhack', 'because browsers!'); }, _cancelEvent: function(e) { // prevent the browser from redirecting to 'because browsers!' e.preventDefault(); }, _setDragEvents: function() { this._dragItems = this._dragItems || []; const items = ReactDOM.findDOMNode(this) .querySelectorAll('[draggable=true]'); const oldItems = []; const newItems = []; for (const item of this._dragItems) { if (items.indexOf(item) < 0) { oldItems.push(item); } } for (const item of items) { if (this._dragItems.indexOf(item) < 0) { oldItems.push(item); } } for (const dragItem of newItems) { dragItem.addEventListener('dragstart', this._listenEvent); dragItem.addEventListener('drop', this._cancelEvent); } for (const dragItem of oldItems) { dragItem.removeEventListener('dragstart', this._listenEvent); dragItem.removeEventListener('drop', this._cancelEvent); } }, render: function() { const sortables = this.state.components.map(function(component, index) {return React.createElement(SortableItem, { index: index, component: component, area: this, key: component.key, draggable: component.props.draggable, dragging: index === this.state.dragging} );}.bind(this) ); return React.createElement("ol", {className: this.props.className, style: this.props.style}, sortables ); }, }); // An individual sortable item const SortableItem = React.createClass({displayName: "SortableItem", propTypes: { area: PT.shape({ onDragEnter: PT.func.isRequired, onDragStart: PT.func.isRequired, onDrop: PT.func.isRequired, }), component: PT.node.isRequired, dragging: PT.bool.isRequired, draggable: PT.bool.isRequired, index: PT.number.isRequired, }, handleDragStart: function(e) { e.nativeEvent.dataTransfer.effectAllowed = "move"; this.props.area.onDragStart(this.props.index); }, handleDrop: function() { this.props.area.onDrop(this.props.index); }, handleDragEnter: function(e) { const verified = this.props.area.onDragEnter(this.props.index); // Ideally this would change the cursor based on whether this is a // valid place to drop. e.nativeEvent.dataTransfer.effectAllowed = verified ? "move" : "none"; }, handleDragOver: function(e) { // allow a drop by preventing default handling e.preventDefault(); }, render: function() { let dragState = "sortable-disabled"; if (this.props.dragging) { dragState = "sortable-dragging"; } else if (this.props.draggable) { dragState = "sortable-enabled"; } return React.createElement("li", {draggable: this.props.draggable, className: dragState, onDragStart: this.handleDragStart, onDrop: this.handleDrop, onDragEnter: this.handleDragEnter, onDragOver: this.handleDragOver }, this.props.component ); }, }); module.exports = SortableArea;