Skip to content

Commit

Permalink
Initial support for native item types
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Mar 18, 2015
1 parent e673826 commit c27c1e2
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 24 deletions.
3 changes: 3 additions & 0 deletions TODO
Expand Up @@ -4,6 +4,9 @@
* check symbols
* helpers for test context
* drag offsets and alignments
* are we okay with this API for refs? ask people & see how that works with actual refs

dnd-core
* smarter canDrop()
* isOver() without canDrop()?
* state extractor on removed target
5 changes: 3 additions & 2 deletions examples/_dustbin-interesting/Container.js
Expand Up @@ -4,7 +4,7 @@ import React, { createClass } from 'react';
import Dustbin from './Dustbin';
import Box from './Box';
import ItemTypes from './ItemTypes';
import { DragDropContext, HTML5Backend } from 'react-dnd';
import { DragDropContext, HTML5Backend, NativeTypes } from 'react-dnd';
import shuffle from 'lodash/collection/shuffle';
import update from 'react/lib/update';

Expand All @@ -18,7 +18,8 @@ const Container = createClass({
dustbins: [
[ItemTypes.GLASS],
[ItemTypes.FOOD],
[ItemTypes.PAPER, ItemTypes.GLASS]
[ItemTypes.PAPER, ItemTypes.GLASS, NativeTypes.URL],
[ItemTypes.PAPER, NativeTypes.FILE]
],
boxes: [
{ name: 'Bottle', type: ItemTypes.GLASS },
Expand Down
3 changes: 2 additions & 1 deletion examples/webpack.config.js
Expand Up @@ -18,7 +18,8 @@ module.exports = {
},
module: {
loaders: [
{ test: /\.js$/, loaders: ['react-hot-loader', '6to5?experimental'], exclude: /node_modules/ }
// TODO: temp dnd-core for npm link
{ test: /\.js$/, loaders: ['6to5?experimental'], exclude: /node_modules|dnd-core/ }
]
},
plugins: [
Expand Down
4 changes: 4 additions & 0 deletions modules/NativeTypes.js
@@ -0,0 +1,4 @@
export default {
FILE: '__NATIVE_FILE__',
URL: '__NATIVE_URL__'
};
150 changes: 129 additions & 21 deletions modules/backends/HTML5.js
@@ -1,7 +1,65 @@
import { DragSource } from 'dnd-core';
import NativeTypes from '../NativeTypes';
import warning from 'react/lib/warning';

function isUrlDataTransfer(dataTransfer) {
var types = Array.prototype.slice.call(dataTransfer.types);
return types.indexOf('Url') > -1 || types.indexOf('text/uri-list') > -1;
}

function isFileDataTransfer(dataTransfer) {
var types = Array.prototype.slice.call(dataTransfer.types);
return types.indexOf('Files') > -1;
}

class FileDragSource extends DragSource {
constructor() {
this.item = {
get files() {
warning(false, 'Browser doesn\'t allow reading file information until the files are dropped.');
return null;
}
};
}

mutateItemByReadingDataTransfer(dataTransfer) {
delete this.item.files;
this.item.files = Array.prototype.slice.call(dataTransfer.files);
}

beginDrag() {
return this.item;
}
}

class UrlDragSource extends DragSource {
constructor() {
this.item = {
get urls() {
warning(false, 'Browser doesn\'t allow reading URL information until the link is dropped.');
return null;
}
};
}

mutateItemByReadingDataTransfer(dataTransfer) {
delete this.item.urls;
this.item.urls = (
dataTransfer.getData('Url') ||
dataTransfer.getData('text/uri-list') || ''
).split('\n');
}

beginDrag() {
return this.item;
}
}

export default class HTML5Backend {
constructor(actions, monitor) {
constructor(actions, monitor, registry) {
this.actions = actions;
this.monitor = monitor;
this.registry = registry;

this.nodeHandlers = {};
this.handleTopDragStart = this.handleTopDragStart.bind(this);
Expand Down Expand Up @@ -55,6 +113,26 @@ export default class HTML5Backend {
return [0, 0];
}

isDraggingNativeItem() {
switch (this.monitor.getItemType()) {
case NativeTypes.FILE:
case NativeTypes.URL:
return true;
default:
return false;
}
}

beginDragNativeUrl() {
const sourceHandle = this.registry.addSource(NativeTypes.URL, new UrlDragSource());
this.actions.beginDrag(sourceHandle);
}

beginDragNativeFile() {
const sourceHandle = this.registry.addSource(NativeTypes.FILE, new FileDragSource());
this.actions.beginDrag(sourceHandle);
}

handleTopDragStartCapture() {
this.dragStartSourceHandles = [];
this.dragStartOriginalTarget = null;
Expand All @@ -81,24 +159,22 @@ export default class HTML5Backend {
}
}

// If none agreed, cancel the dragging.
if (!this.monitor.isDragging()) {
const { dataTransfer } = e;
if (this.monitor.isDragging()) {
// If child drag source refuses drag but parent agrees,
// use parent's node as drag image. This won't work in IE.
const dragOffset = this.getDragImageOffset(node);
dataTransfer.setDragImage(node, ...dragOffset);
dataTransfer.setData('application/json', {});

this.dragStartOriginalTarget = e.target;
} else if (isUrlDataTransfer(dataTransfer)) {
// URL dragged from inside the document
this.beginDragNativeUrl();
} else {
// If by this time no drag source reacted, tell browser not to drag.
e.preventDefault();
return;
}

// Save the original target so we can later check
// dragend events against it.
this.dragStartOriginalTarget = e.target;

// Specify backend's MIME so other backends
// don't interfere with this drag operation.
e.dataTransfer.setData(this.mime, {});

// If child drag source refuses drag but parent agrees,
// use parent's node as drag image. This won't work in IE.
const dragOffset = this.getDragImageOffset(node);
e.dataTransfer.setDragImage(node, ...dragOffset);
}

handleTopDragEndCapture() {
Expand All @@ -116,8 +192,12 @@ export default class HTML5Backend {
handleTopDragEnd() {
}

handleTopDragOverCapture() {
handleTopDragOverCapture(e) {
this.dragOverTargetHandles = [];

if (this.isDraggingNativeItem()) {
e.preventDefault();
}
}

handleDragOver(e, targetHandle) {
Expand All @@ -140,8 +220,21 @@ export default class HTML5Backend {
}
}

handleTopDragEnterCapture() {
handleTopDragEnterCapture(e) {
this.dragEnterTargetHandles = [];

if (this.monitor.isDragging()) {
return;
}

const { dataTransfer } = e;
if (isFileDataTransfer(dataTransfer)) {
// File dragged from outside the document
this.beginDragNativeFile();
} else if (isUrlDataTransfer(dataTransfer)) {
// URL dragged from outside the document
this.beginDragNativeUrl();
}
}

handleDragEnter(e, targetHandle) {
Expand All @@ -164,14 +257,25 @@ export default class HTML5Backend {
}
}

handleTopDragLeaveCapture() {
handleTopDragLeaveCapture(e) {
if (this.isDraggingNativeItem()) {
e.preventDefault();
}
}

handleTopDragLeave() {
}

handleTopDropCapture() {
handleTopDropCapture(e) {
this.dropTargetHandles = [];

if (this.isDraggingNativeItem()) {
e.preventDefault();

const sourceHandle = this.monitor.getSourceHandle();
const source = this.registry.getSource(sourceHandle);
source.mutateItemByReadingDataTransfer(e.dataTransfer);
}
}

handleDrop(e, targetHandle) {
Expand All @@ -184,6 +288,10 @@ export default class HTML5Backend {

this.actions.hover(dropTargetHandles);
this.actions.drop();

if (this.isDraggingNativeItem()) {
this.actions.endDrag();
}
}

updateSourceNode(sourceHandle, node) {
Expand Down
1 change: 1 addition & 0 deletions modules/index.js
@@ -1,6 +1,7 @@
export { default as HTML5Backend } from './backends/HTML5';
export { default as DragSource } from './ReactDragSource';
export { default as DropTarget } from './ReactDropTarget';
export { default as NativeTypes } from './NativeTypes';

// We want need those after React 0.14:
export { default as DragDropContext } from './DragDropContext';
Expand Down

0 comments on commit c27c1e2

Please sign in to comment.