├── .babelrc
├── .eslintignore
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── docs
├── 00 Quick Start
│ ├── FAQ.md
│ ├── Overview.md
│ ├── Testing.md
│ ├── Troubleshooting.md
│ └── Tutorial.md
├── 01 Top Level API
│ ├── DragDropContext.md
│ ├── DragLayer.md
│ ├── DragSource.md
│ └── DropTarget.md
├── 02 Connecting to DOM
│ ├── DragSourceConnector.md
│ └── DropTargetConnector.md
├── 03 Monitoring State
│ ├── DragLayerMonitor.md
│ ├── DragSourceMonitor.md
│ └── DropTargetMonitor.md
├── 04 Backends
│ ├── HTML5.md
│ └── Test.md
├── README.md
└── index.md
├── examples
├── 00 Chessboard
│ └── Tutorial App
│ │ ├── Board.js
│ │ ├── Board.less
│ │ ├── BoardSquare.js
│ │ ├── Constants.js
│ │ ├── Game.js
│ │ ├── Knight.js
│ │ ├── Square.js
│ │ └── index.js
├── 01 Dustbin
│ ├── Multiple Targets
│ │ ├── Box.js
│ │ ├── Container.js
│ │ ├── Dustbin.js
│ │ ├── ItemTypes.js
│ │ └── index.js
│ ├── Single Target
│ │ ├── Box.js
│ │ ├── Container.js
│ │ ├── Dustbin.js
│ │ ├── ItemTypes.js
│ │ ├── __tests__
│ │ │ └── Box-test.js
│ │ └── index.js
│ └── Stress Test
│ │ ├── Box.js
│ │ ├── Container.js
│ │ ├── Dustbin.js
│ │ ├── ItemTypes.js
│ │ └── index.js
├── 02 Drag Around
│ ├── Custom Drag Layer
│ │ ├── Box.js
│ │ ├── BoxDragPreview.js
│ │ ├── Container.js
│ │ ├── CustomDragLayer.js
│ │ ├── DraggableBox.js
│ │ ├── ItemTypes.js
│ │ ├── index.js
│ │ ├── shallowEqual.js
│ │ ├── shouldPureComponentUpdate.js
│ │ └── snapToGrid.js
│ └── Naive
│ │ ├── Box.js
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ └── index.js
├── 03 Nesting
│ ├── Drag Sources
│ │ ├── Colors.js
│ │ ├── Container.js
│ │ ├── SourceBox.js
│ │ ├── TargetBox.js
│ │ └── index.js
│ └── Drop Targets
│ │ ├── Box.js
│ │ ├── Container.js
│ │ ├── Dustbin.js
│ │ ├── ItemTypes.js
│ │ └── index.js
├── 04 Sortable
│ ├── Cancel on Drop Outside
│ │ ├── Card.js
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ └── index.js
│ ├── Simple
│ │ ├── Card.js
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ └── index.js
│ └── Stress Test
│ │ ├── Card.js
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ └── index.js
├── 05 Customize
│ ├── Drop Effects
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ ├── SourceBox.js
│ │ ├── TargetBox.js
│ │ └── index.js
│ └── Handles and Previews
│ │ ├── BoxWithHandle.js
│ │ ├── BoxWithImage.js
│ │ ├── Container.js
│ │ ├── ItemTypes.js
│ │ └── index.js
├── README.md
└── shared
│ └── wrapInTestContext.js
├── karma.conf.js
├── package.json
├── scripts
├── build.sh
├── buildSiteIndexPages.sh
├── buildStaticSite.sh
├── cssTransformLoader.js
├── markdownLoader.js
├── prism.js
├── publishStaticSite.sh
├── resolvers.js
└── startSiteDevServer.sh
├── site
├── Constants.js
├── IndexPage.js
├── LICENSE
├── README.md
├── base.less
├── client.js
├── components
│ ├── CodeBlock.js
│ ├── CodeBlock.less
│ ├── Cover.js
│ ├── Cover.less
│ ├── Header.js
│ ├── Header.less
│ ├── NavBar.js
│ ├── NavBar.less
│ ├── PageBody.js
│ ├── PageBody.less
│ ├── SideBar.js
│ ├── SideBar.less
│ └── StaticHTMLBlock.js
├── constants.less
├── pages
│ ├── APIPage.js
│ ├── ExamplePage.js
│ └── HomePage.js
├── renderPath.js
├── webpack-client.config.js
└── webpack-prerender.config.js
├── src
├── DragDropContext.js
├── DragLayer.js
├── DragSource.js
├── DropTarget.js
├── areOptionsEqual.js
├── createSourceConnector.js
├── createSourceFactory.js
├── createSourceMonitor.js
├── createTargetConnector.js
├── createTargetFactory.js
├── createTargetMonitor.js
├── decorateHandler.js
├── index.js
├── registerSource.js
├── registerTarget.js
├── utils
│ ├── checkDecoratorArguments.js
│ ├── cloneWithRef.js
│ ├── isValidType.js
│ ├── shallowEqual.js
│ └── shallowEqualScalar.js
└── wrapConnectorHooks.js
├── test-utils.js
├── tests.webpack.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"],
3 | "plugins": [
4 | "transform-class-properties",
5 | "transform-decorators-legacy",
6 | ["transform-react-jsx", {
7 | "pragma": "preact.h"
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | __site__
2 | __site_prerender__
3 | site
4 | scripts
5 | dist
6 | lib
7 | **/node_modules
8 | **/webpack*.config.js
9 | tests.webpack.js
10 | karma.conf.js
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | lib
4 | .DS_Store
5 | .idea
6 | __site__
7 | __site_prerender__
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | scripts
3 | examples
4 | site
5 | __site__
6 | __site_prerender__
7 | webpack.config.js
8 | .babelrc
9 | .eslintrc
10 | bower.json
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs-2"
4 | script:
5 | - npm run lint
6 | - npm run build
7 | before_install:
8 | - npm install -g npm@3.x
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Dan Abramov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Drag and Drop for Preact
2 | =========
3 |
4 | _This is a form of [react-dnd](https://github.com/react-dnd/react-dnd) made by [@gaearon](https://github.com/gaearon)_
5 |
6 | Docs could be looked at `react-dnd` docs page since API is the same (replace React things with Preact ones):
7 |
8 | https://react-dnd.github.io/react-dnd/
9 |
--------------------------------------------------------------------------------
/docs/00 Quick Start/Testing.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | Testing
4 | ===================
5 |
6 | React DnD is test-friendly. The whole drag and drop interaction, including the rendering of your components, as well as the side effects you perform in response to the drag and drop events, can be tested.
7 |
8 | There are several different approaches to testing React components. React DnD is not opinionated and lets you use either of them. **Neither of those approaches require the browser event system to be available.**
9 |
10 | A few test examples are included with the React DnD inside its `examples` folder. Run `npm install` and `npm test` inside the `react-dnd` root folder to start them.
11 |
12 | ### Testing the Components in Isolation
13 |
14 | If you are only interested in testing the *rendering* of your components in isolation, and not their interaction, you may use the `DecoratedComponent` static property available on any class wrapped with React DnD to access the original class. You may then test it with the different props without any dependency on React DnD, supplying an identity function to stub the connector methods.
15 |
16 | -------------------
17 | ```js
18 | var React = require('react');
19 | var TestUtils = require('react-addons-test-utils');
20 | var expect = require('expect');
21 |
22 | it('can be tested independently', function () {
23 | // Obtain the reference to the component before React DnD wrapping
24 | var OriginalBox = Box.DecoratedComponent;
25 |
26 | // Stub the React DnD connector functions with an identity function
27 | var identity = function (el) { return el; };
28 |
29 | // Render with one set of props and test
30 | var root = TestUtils.renderIntoDocument(
31 |
33 | );
34 | var div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
35 | expect(div.props.style.opacity).toEqual(1);
36 |
37 | // Render with another set of props and test
38 | root = TestUtils.renderIntoDocument(
39 |
42 | );
43 | div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
44 | expect(div.props.style.opacity).toEqual(0.4);
45 | });
46 | ```
47 | -------------------
48 | ```js
49 | import React from 'react';
50 | import TestUtils from 'react-addons-test-utils';
51 | import expect from 'expect';
52 |
53 | it('can be tested independently', () => {
54 | // Obtain the reference to the component before React DnD wrapping
55 | const OriginalBox = Box.DecoratedComponent;
56 |
57 | // Stub the React DnD connector functions with an identity function
58 | const identity = el => el;
59 |
60 | // Render with one set of props and test
61 | let root = TestUtils.renderIntoDocument(
62 |
64 | );
65 | let div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
66 | expect(div.props.style.opacity).toEqual(1);
67 |
68 | // Render with another set of props and test
69 | root = TestUtils.renderIntoDocument(
70 |
73 | );
74 | div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
75 | expect(div.props.style.opacity).toEqual(0.4);
76 | });
77 | ```
78 | -------------------
79 |
80 | -------------------
81 |
82 | ### Testing the Drag and Drop Interaction
83 |
84 | If you want to test the whole interaction, and not just the individual component rendering, you should use the [test backend](docs-test-backend.html). **The test backend does not require the DOM** so you can also use it with [`ReactShallowRenderer`](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) just fine.
85 |
86 | This is currently the least documented part of React DnD because it exposes the underlying concepts from the [DnD Core](https://github.com/gaearon/dnd-core), the library powering React DnD inside. You can learn more about the test backend and its methods from the [DnD Core tests](https://github.com/gaearon/dnd-core/tree/v1.1.0/src/__tests__).
87 |
88 | First, you need to install the test backend:
89 |
90 | ```
91 | npm install --save-dev react-dnd-test-backend
92 | ```
93 |
94 | Here is an example to get you started:
95 |
96 | -------------------
97 | ```js
98 | var React = require('react');
99 | var Component = React.Component;
100 | var TestBackend = require('react-dnd-test-backend');
101 | var DragDropContext = require('react-dnd').DragDropContext;
102 | var TestUtils = require('react-addons-test-utils');
103 | var expect = require('expect');
104 |
105 | /**
106 | * Wraps a component into a DragDropContext that uses the TestBackend.
107 | */
108 | function wrapInTestContext(DecoratedComponent) {
109 | return DragDropContext(TestBackend)(
110 | React.createClass({
111 | render: function () {
112 | return ;
113 | }
114 | })
115 | );
116 | }
117 |
118 | it('can be tested with the testing backend', function () {
119 | // Render with the test context that uses the test backend
120 | var BoxContext = wrapInTestContext(Box);
121 | var root = TestUtils.renderIntoDocument( );
122 |
123 | // Obtain a reference to the backend
124 | var backend = root.getManager().getBackend();
125 |
126 | // Test that the opacity is 1
127 | let div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
128 | expect(div.props.style.opacity).toEqual(1);
129 |
130 | // Find the drag source ID and use it to simulate the dragging operation
131 | var box = TestUtils.findRenderedComponentWithType(root, Box);
132 | backend.simulateBeginDrag([box.getHandlerId()]);
133 |
134 | // Optionally you can pass in a clientOffset for testing operations that
135 | // depend on mouse movements.
136 | // backend.simulateBeginDrag([box.getHandlerId()], {
137 | // clientOffset: { x: 0, y: 0 },
138 | // getSourceClientOffset: () => ({ x: 0, y: 0 }),
139 | // });
140 |
141 | // Verify that the div changed its opacity
142 | div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
143 | expect(div.props.style.opacity).toEqual(0.4);
144 |
145 | // See other backend.simulate* methods for more!
146 | });
147 | ```
148 | -------------------
149 | ```js
150 | import React, { Component } from 'react';
151 | import TestBackend from 'react-dnd-test-backend';
152 | import { DragDropContext } from 'react-dnd';
153 | import TestUtils from 'react-addons-test-utils';
154 | import expect from 'expect';
155 |
156 | /**
157 | * Wraps a component into a DragDropContext that uses the TestBackend.
158 | */
159 | function wrapInTestContext(DecoratedComponent) {
160 | return DragDropContext(TestBackend)(
161 | class TestContextContainer extends Component {
162 | render() {
163 | return ;
164 | }
165 | }
166 | );
167 | }
168 |
169 | it('can be tested with the testing backend', () => {
170 | // Render with the test context that uses the test backend
171 | const BoxContext = wrapInTestContext(Box);
172 | const root = TestUtils.renderIntoDocument( );
173 |
174 | // Obtain a reference to the backend
175 | const backend = root.getManager().getBackend();
176 |
177 | // Test that the opacity is 1
178 | let div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
179 | expect(div.props.style.opacity).toEqual(1);
180 |
181 | // Find the drag source ID and use it to simulate the dragging operation
182 | const box = TestUtils.findRenderedComponentWithType(root, Box);
183 | backend.simulateBeginDrag([box.getHandlerId()]);
184 |
185 | // Verify that the div changed its opacity
186 | div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
187 | expect(div.props.style.opacity).toEqual(0.4);
188 |
189 | // See other backend.simulate* methods for more!
190 | });
191 | ```
192 | -------------------
193 | ```js
194 | import React, { Component } from 'react';
195 | import TestBackend from 'react-dnd-test-backend';
196 | import { DragDropContext } from 'react-dnd';
197 | import TestUtils from 'react-addons-test-utils';
198 | import expect from 'expect';
199 |
200 | /**
201 | * Wraps a component into a DragDropContext that uses the TestBackend.
202 | */
203 | function wrapInTestContext(DecoratedComponent) {
204 | @DragDropContext(TestBackend)
205 | class TestContextContainer extends Component {
206 | render() {
207 | return ;
208 | }
209 | }
210 |
211 | return TestContextContainer;
212 | }
213 |
214 | it('can be tested with the testing backend', () => {
215 | // Render with the test context that uses the test backend
216 | const BoxContext = wrapInTestContext(Box);
217 | const root = TestUtils.renderIntoDocument( );
218 |
219 | // Obtain a reference to the backend
220 | const backend = root.getManager().getBackend();
221 |
222 | // Test that the opacity is 1
223 | let div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
224 | expect(div.props.style.opacity).toEqual(1);
225 |
226 | // Find the drag source ID and use it to simulate the dragging operation
227 | const box = TestUtils.findRenderedComponentWithType(root, Box);
228 | backend.simulateBeginDrag([box.getHandlerId()]);
229 |
230 | // Verify that the div changed its opacity
231 | div = TestUtils.findRenderedDOMComponentWithTag(root, 'div');
232 | expect(div.props.style.opacity).toEqual(0.4);
233 |
234 | // See other backend.simulate* methods for more!
235 | });
236 | ```
237 | -------------------
238 |
--------------------------------------------------------------------------------
/docs/00 Quick Start/Troubleshooting.md:
--------------------------------------------------------------------------------
1 | Troubleshooting
2 | ===================
3 |
4 | This page is dedicated to the problems you might bump into while using React DnD.
5 |
6 |
7 | ### Could not find the drag and drop manager in the context
8 |
9 | There are several ways this error might have happened:
10 |
11 | 1. You wrapped some component with a [`DragSource`](docs-drag-source.html), [`DropTarget`](docs-drop-target.html), or [`DragLayer`](docs-drag-layer.html), but forgot to wrap your top-level component with the [`DragDropContext`](docs-drag-drop-context.html).
12 |
13 | 2. You *have* wrapped the top-level component with the [`DragDropContext`](docs-drag-drop-context.html) but exported an unwrapped version by mistake.
14 |
15 | 3. You are using a component in an isolated environment like a unit test. See the second point for instructions to stub it.
16 |
17 | 4. The context is lost for some other reason. [Please file an issue](https://github.com/gaearon/react-dnd/issues/new) if you can reproduce it on a small project.
18 |
19 | #### Wrap top-level component with a `DragDropContext`
20 |
21 | To fix this error, find the top-level component in your app. Usually, this is the root route handler. It doesn't have to be the topmost component, but it has to be higher in the hierarchy than any of your component using React DnD. Wrap this component with a [`DragDropContext`](docs-drag-drop-context.html) call. It is important to specify a *backend* as the only argument in that call. Currently React DnD only provides an [`HTML5` backend](docs-html5-backend.html), but touch backend support is coming in the future.
22 |
23 | #### Stub the context inside the unit tests
24 |
25 | If you have this error in a test, [read the testing guide](docs-testing.html) for instructions on stubbing the context.
26 |
27 | #### Make sure you that don't have duplicate React
28 |
29 | This problem may also appear if you have a duplicate React installation in your Browserify or Webpack build. [This article](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375) explains both the problem and the solution to it.
30 |
31 | ### React DnD does not provide a default export
32 |
33 | React DnD does not provide a [default export](http://www.2ality.com/2014/09/es6-modules-final.html).
34 | Mind the difference:
35 |
36 | -------------------
37 | ```js
38 | // Wrong:
39 | var DragSource = require('react-dnd');
40 |
41 | // Correct:
42 | var DragSource = require('react-dnd').DragSource;
43 | ```
44 | -------------------
45 | ```js
46 | // Wrong:
47 | import DragSource from 'react-dnd';
48 |
49 | // Correct:
50 | import { DragSource } from 'react-dnd';
51 | ```
52 | -------------------
53 |
54 | -------------------
55 |
56 | ### You seem to be applying the arguments in the wrong order
57 |
58 | For the [`DragSource`](docs-drag-source.html), [`DropTarget`](docs-drop-target.html), [`DragLayer`](docs-drag-layer.html), and the [`DragDropContext`](docs-drag-drop-context.html), it is important that you first pass them the configuration arguments, and *then* inject your React component in a second call.
59 |
60 | -------------------
61 | ```js
62 | // Wrong:
63 | module.exports = DragSource(YourComponent)(/* ... */);
64 | module.exports = DragSource(YourComponent, /* ... */);
65 |
66 | // Correct:
67 | module.exports = DragSource(/* ... */)(YourComponent);
68 | ```
69 | -------------------
70 | ```js
71 | // Wrong:
72 | export default DragSource(YourComponent)(/* ... */);
73 | export default DragSource(YourComponent, /* ... */);
74 |
75 | // Correct:
76 | export default DragSource(/* ... */)(YourComponent);
77 | ```
78 | -------------------
79 |
80 | -------------------
81 |
82 | Remember, **the component comes last!**
83 |
84 | You may ask why I chose such a weird API. In fact it's not *too* weird, but the neat part about it is that, if you enable the [ES7 decorators](https://github.com/wycats/javascript-decorators) in [Babel](https://babeljs.io) by putting `{ "stage": 1 }` in your [.babelrc file](https://babeljs.io/docs/usage/babelrc/), you can write them naturally:
85 |
86 | -------------------
87 |
88 | -------------------
89 |
90 | -------------------
91 | ```js
92 | @DragSource(/* ... */)
93 | export default class YourComponent { }
94 | ```
95 | -------------------
96 |
97 | You may not use ES7 decorators today (linters don't understand them), but this API lets you switch to the decorators when they are more mature, and also has [nicer composition semantics](docs-faq.html#how-do-i-combine-several-drag-sources-and-drop-targets-in-a-single-component-) in ES5 or ES6.
--------------------------------------------------------------------------------
/docs/01 Top Level API/DragDropContext.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | DragDropContext
4 | =========================
5 |
6 | Wrap the root component of your application with `DragDropContext` to set up React DnD.
7 | This lets you specify the backend, and sets up the shared DnD state behind the scenes.
8 |
9 | ### Usage
10 |
11 | -------------------
12 | ```js
13 | var HTML5Backend = require('react-dnd-html5-backend');
14 | var DragDropContext = require('react-dnd').DragDropContext;
15 |
16 | var YourApp = React.createClass(
17 | /* ... */
18 | );
19 |
20 | module.exports = DragDropContext(HTML5Backend)(YourApp);
21 | ```
22 | -------------------
23 | ```js
24 | import HTML5Backend from 'react-dnd-html5-backend';
25 | import { DragDropContext } from 'react-dnd';
26 |
27 | class YourApp {
28 | /* ... */
29 | }
30 |
31 | export default DragDropContext(HTML5Backend)(YourApp);
32 | ```
33 | -------------------
34 | ```js
35 | import HTML5Backend from 'react-dnd-html5-backend';
36 | import { DragDropContext } from 'react-dnd';
37 |
38 | @DragDropContext(HTML5Backend)
39 | export default class YourApp {
40 | /* ... */
41 | }
42 | ```
43 | -------------------
44 |
45 | ### Parameters
46 |
47 | * **`backend`**: Required. A React DnD backend. Unless you're writing a custom one, you probably want to use the [HTML5 backend](docs-html5-backend.html) that ships with React DnD.
48 |
49 | ### Return Value
50 |
51 | `DragDropContext` wraps your component and returns another React component.
52 | For easier [testing](docs-testing.html), it provides an API to reach into the internals:
53 |
54 | #### Static Properties
55 |
56 | * **`DecoratedComponent`**: Returns the wrapped component type.
57 |
58 | #### Instance Methods
59 |
60 | * **`getDecoratedComponentInstance()`**: Returns the wrapped component instance.
61 |
62 | * **`getManager()`**: Returns the internal manager that provides access to the underlying backend. This part is currently not documented, but you can refer to the [testing](docs-testing.html) tutorial for a usage example.
63 |
--------------------------------------------------------------------------------
/docs/02 Connecting to DOM/DropTargetConnector.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | DropTargetConnector
4 | ===================
5 |
6 | `DropTargetConnector` is an object passed to a collecting function of the [`DropTarget`](docs-drop-target.html). Its only method `dropTarget()` returns a function that lets you assign the drop target role to one of your component's DOM nodes.
7 |
8 | ### Methods
9 |
10 | Call the `DropTargetConnector`'s `dropTarget()` method inside your *collecting function*. This will pass the returned function to your component as a prop. You can then use it inside `render` or `componentDidMount` to indicate a DOM node should react to drop target events. Internally it works by attaching a [callback ref](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) to the React element you give it. This callback connects the DOM node of your component to the chosen DnD backend.
11 |
12 | * **`dropTarget() => (elementOrNode)`**: Returns a function that must be used inside the component to assign the drop target role to a node. By returning `{ connectDropTarget: connect.dropTarget() }` from your collecting function, you can mark any React element as the droppable node. To do that, replace any `element` with `this.props.connectDropTarget(element)` inside the `render` function.
13 |
14 | ### Example
15 |
16 | Check out [the tutorial](docs-tutorial.html) for more real examples!
17 |
18 | -------------------
19 | ```js
20 | var React = require('react');
21 | var DropTarget = require('react-dnd').DropTarget;
22 |
23 | /* ... */
24 |
25 | function collect(connect, monitor) {
26 | return {
27 | connectDropTarget: connect.dropTarget()
28 | };
29 | }
30 |
31 | var DropZone = React.createClass({
32 | render: function () {
33 | var connectDropTarget = this.props.connectDropTarget;
34 |
35 | return connectDropTarget(
36 |
37 | You can drop here!
38 |
39 | );
40 | }
41 | });
42 |
43 | module.exports = DropTarget(/* ... */)(DropZone);
44 | ```
45 | -------------------
46 | ```js
47 | import React from 'react';
48 | import { DropTarget } from 'react-dnd';
49 |
50 | /* ... */
51 |
52 | function collect(connect, monitor) {
53 | return {
54 | connectDropTarget: connect.dropTarget()
55 | };
56 | }
57 |
58 | class DropZone {
59 | render() {
60 | const { connectDropTarget } = this.props;
61 |
62 | return connectDropTarget(
63 |
64 | You can drop here!
65 |
66 | );
67 | }
68 | }
69 |
70 | export default DropTarget(/* ... */)(DropZone);
71 | ```
72 | -------------------
73 | ```js
74 | import React from 'react';
75 | import { DropTarget } from 'react-dnd';
76 |
77 | /* ... */
78 |
79 | function collect(connect, monitor) {
80 | return {
81 | connectDropTarget: connect.dropTarget()
82 | };
83 | }
84 |
85 | @DropTarget(/* ... */)
86 | export default class DropZone {
87 | render() {
88 | const { connectDropTarget } = this.props;
89 |
90 | return connectDropTarget(
91 |
92 | You can drop here!
93 |
94 | );
95 | }
96 | }
97 | ```
98 | -------------------
99 |
100 |
--------------------------------------------------------------------------------
/docs/03 Monitoring State/DragLayerMonitor.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | DragLayerMonitor
4 | ===================
5 |
6 | `DragLayerMonitor` is an object passed to a collecting function of the [`DragLayer`](docs-drag-layer.html). Its methods let you get information about the global drag state.
7 |
8 | ### Methods
9 |
10 | * **`isDragging()`**: Returns `true` if a drag operation is in progress. Returns `false` otherwise.
11 |
12 | * **`getItemType()`**: Returns a string or an ES6 symbol identifying the type of the current dragged item. Returns `null` if no item is being dragged.
13 |
14 | * **`getItem()`**: Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its `beginDrag()` method. Returns `null` if no item is being dragged.
15 |
16 | * **`getInitialClientOffset()`**: Returns the `{ x, y }` client offset of the pointer at the time when the current drag operation has started. Returns `null` if no item is being dragged.
17 |
18 | * **`getInitialSourceClientOffset()`**: Returns the `{ x, y }` client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns `null` if no item is being dragged.
19 |
20 | * **`getClientOffset`**: Returns the last recorded `{ x, y }` client offset of the pointer while a drag operation is in progress. Returns `null` if no item is being dragged.
21 |
22 | * **`getDifferenceFromInitialOffset()`**: Returns the `{ x, y }` difference between the last recorded client offset of the pointer and the client offset when current the drag operation has started. Returns `null` if no item is being dragged.
23 |
24 | * **`getSourceClientOffset()`**: Returns the projected `{ x, y }` client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns `null` if no item is being dragged.
--------------------------------------------------------------------------------
/docs/03 Monitoring State/DragSourceMonitor.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | DragSourceMonitor
4 | ===================
5 |
6 | `DragSourceMonitor` is an object passed to a collecting function of the [`DragSource`](docs-drag-source.html). Its methods let you get information about the drag state of a specific drag source. The specific drag source bound to that monitor is called the monitor's *owner* below.
7 |
8 | ### Methods
9 |
10 | * **`canDrag()`**: Returns `true` if no drag operation is in progress, and the owner's `canDrag()` returns `true` or is not defined.
11 |
12 | * **`isDragging()`**: Returns `true` if there is a drag operation is in progress, and either the owner initiated the drag, or its `isDragging()` is defined and returns `true`.
13 |
14 | * **`getItemType()`**: Returns a string or an ES6 symbol identifying the type of the current dragged item. Returns `null` if no item is being dragged.
15 |
16 | * **`getItem()`**: Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its `beginDrag()` method. Returns `null` if no item is being dragged.
17 |
18 | * **`getDropResult()`**: Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their `drop()` methods. When a chain of `drop()` is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from `drop()` overrides the child drop result previously set by the child. Returns `null` if called outside `endDrag()`.
19 |
20 | * **`didDrop()`** Returns `true` if some drop target has handled the drop event, `false` otherwise. Even if a target did not return a drop result, `didDrop()` returns `true`. Use it inside `endDrag()` to test whether any drop target has handled the drop. Returns `false` if called outside `endDrag()`.
21 |
22 | * **`getInitialClientOffset()`**: Returns the `{ x, y }` client offset of the pointer at the time when the current drag operation has started. Returns `null` if no item is being dragged.
23 |
24 | * **`getInitialSourceClientOffset()`**: Returns the `{ x, y }` client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns `null` if no item is being dragged.
25 |
26 | * **`getClientOffset()`**: Returns the last recorded `{ x, y }` client offset of the pointer while a drag operation is in progress. Returns `null` if no item is being dragged.
27 |
28 | * **`getDifferenceFromInitialOffset()`**: Returns the `{ x, y }` difference between the last recorded client offset of the pointer and the client offset when current the drag operation has started. Returns `null` if no item is being dragged.
29 |
30 | * **`getSourceClientOffset()`**: Returns the projected `{ x, y }` client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns `null` if no item is being dragged.
31 |
--------------------------------------------------------------------------------
/docs/03 Monitoring State/DropTargetMonitor.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | DropTargetMonitor
4 | ===================
5 |
6 | `DropTargetMonitor` is an object passed to a collecting function of the [`DropTarget`](docs-drop-target.html). Its methods let you get information about the drag state of a specific drop target. The specific drop target bound to that monitor is called the monitor's *owner* below.
7 |
8 | ### Methods
9 |
10 | * **`canDrop()`**: Returns `true` if no drag operation is in progress, and the owner's `canDrop()` returns `true` or is not defined.
11 |
12 | * **`isOver(options)`**: Returns `true` if there is a drag operation is in progress, and the pointer is currently hovering over the owner. You may optionally pass `{ shallow: true }` to strictly check whether *only* the owner is being hovered, as opposed to a nested target.
13 |
14 | * **`getItemType()`**: Returns a string or an ES6 symbol identifying the type of the current dragged item. Returns `null` if no item is being dragged.
15 |
16 | * **`getItem()`**: Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its `beginDrag()` method. Returns `null` if no item is being dragged.
17 |
18 | * **`getDropResult()`**: Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their `drop()` methods. When a chain of `drop()` is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from `drop()` overrides the drop result previously set by the child. Returns `null` if called outside `drop()`.
19 |
20 | * **`didDrop()`** Returns `true` if some drop target has handled the drop event, `false` otherwise. Even if a target did not return a drop result, `didDrop()` returns `true`. Use it inside `drop()` to test whether any nested drop target has already handled the drop. Returns `false` if called outside `drop()`.
21 |
22 | * **`getInitialClientOffset()`**: Returns the `{ x, y }` client offset of the pointer at the time when the current drag operation has started. Returns `null` if no item is being dragged.
23 |
24 | * **`getInitialSourceClientOffset()`**: Returns the `{ x, y }` client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns `null` if no item is being dragged.
25 |
26 | * **`getClientOffset()`**: Returns the last recorded `{ x, y }` client offset of the pointer while a drag operation is in progress. Returns `null` if no item is being dragged.
27 |
28 | * **`getDifferenceFromInitialOffset()`**: Returns the `{ x, y }` difference between the last recorded client offset of the pointer and the client offset when current the drag operation has started. Returns `null` if no item is being dragged.
29 |
30 | * **`getSourceClientOffset()`**: Returns the projected `{ x, y }` client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns `null` if no item is being dragged.
31 |
--------------------------------------------------------------------------------
/docs/04 Backends/HTML5.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | HTML5
4 | ===================
5 |
6 | This is the only officially supported backend for React DnD. It uses [the HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_and_drop) under the hood and hides [its quirks](http://quirksmode.org/blog/archives/2009/09/the_html5_drag.html).
7 |
8 | ### Installation
9 |
10 | The HTML5 backend comes in a separate package:
11 |
12 | ```
13 | npm install --save react-dnd-html5-backend
14 | ```
15 |
16 | While we suggest you to use NPM, you can find the precompiled UMD build in the `dist` folder available on the [latest](https://github.com/gaearon/react-dnd-html5-backend/tree/latest/dist) branch as well as in every [tagged release](https://github.com/gaearon/react-dnd-html5-backend/releases). This is where you can point Bower if that’s what you use.
17 |
18 | ### Extras
19 |
20 | Aside from the default export, the HTML5 backend module also provides a few extras:
21 |
22 | * **`getEmptyImage()`**: a function returning a transparent empty [`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image). Use `connect.dragPreview()` of the [DragSourceConnector](docs-drag-source-connector.html) to hide the browser-drawn drag preview completely. Handy for drawing the [custom drag layers with `DragLayer`](docs-drag-layer.html). Note that the custom drag previews don't work in IE.
23 |
24 | * **`NativeTypes`**: an enumeration of three constants, `NativeTypes.FILE`, `NativeTypes.URL` and `NativeTypes.TEXT`. You may register the [drop targets](docs-drop-target.html) for these types to handle the drop of the native files, URLs, or the regular page text.
25 |
26 | ### Usage
27 |
28 | -------------------
29 | ```js
30 | var HTML5Backend = require('react-dnd-html5-backend');
31 | var DragDropContext = require('react-dnd').DragDropContext;
32 |
33 | var YourApp = React.createClass(
34 | /* ... */
35 | );
36 |
37 | module.exports = DragDropContext(HTML5Backend)(YourApp);
38 | ```
39 | -------------------
40 | ```js
41 | import HTML5Backend from 'react-dnd-html5-backend';
42 | import { DragDropContext } from 'react-dnd';
43 |
44 | class YourApp {
45 | /* ... */
46 | }
47 |
48 | export default DragDropContext(HTML5Backend)(YourApp);
49 | ```
50 | -------------------
51 | ```js
52 | import HTML5Backend from 'react-dnd-html5-backend';
53 | import { DragDropContext } from 'react-dnd';
54 |
55 | @DragDropContext(HTML5Backend)
56 | export default class YourApp {
57 | /* ... */
58 | }
59 | ```
60 | -------------------
61 |
62 |
--------------------------------------------------------------------------------
/docs/04 Backends/Test.md:
--------------------------------------------------------------------------------
1 | *New to React DnD? [Read the overview](docs-overview.html) before jumping into the docs.*
2 |
3 | Test
4 | ===================
5 |
6 | The test backend lets you test the drag and drop interaction of your components without the DOM.
7 |
8 | ### Installation
9 |
10 | The test backend comes in a separate package:
11 |
12 | ```
13 | npm install --save-dev react-dnd-test-backend
14 | ```
15 |
16 | ### Usage
17 |
18 | See the second example in the [testing](docs-testing.html) tutorial.
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | The docs are not meant to be read on Github.
2 | Read the docs on the official React DnD website instead:
3 |
4 | http://gaearon.github.io/react-dnd/docs-overview.html
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | React DnD is a set of React [higher-order](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) components to help you build complex drag and drop interfaces while keeping your components decoupled. It is a perfect fit for apps like Trello and Storify, where dragging transfers data between different parts of the application, and the components change their appearance and the application state in response to the drag and drop events.
2 |
3 | ## Installation
4 |
5 | ```sh
6 | npm install --save react-dnd
7 | npm install --save react-dnd-html5-backend
8 | ```
9 |
10 | The second package instructs React DnD to use [the HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_and_drop) under the hood. You may choose to use a third-party backend instead, such as [the touch backend](https://github.com/yahoo/react-dnd-touch-backend).
11 |
12 | ## What's It Look Like?
13 |
14 | -------------------
15 | ```js
16 | // Let's make draggable!
17 |
18 | var React = require('react');
19 | var DragSource = require('react-dnd').DragSource;
20 | var ItemTypes = require('./Constants').ItemTypes;
21 | var PropTypes = React.PropTypes;
22 |
23 | /**
24 | * Implements the drag source contract.
25 | */
26 | var cardSource = {
27 | beginDrag: function (props) {
28 | return {
29 | text: props.text
30 | };
31 | }
32 | }
33 |
34 | /**
35 | * Specifies the props to inject into your component.
36 | */
37 | function collect(connect, monitor) {
38 | return {
39 | connectDragSource: connect.dragSource(),
40 | isDragging: monitor.isDragging()
41 | };
42 | }
43 |
44 | var Card = React.createClass({
45 | propTypes: {
46 | text: PropTypes.string.isRequired,
47 |
48 | // Injected by React DnD:
49 | isDragging: PropTypes.bool.isRequired,
50 | connectDragSource: PropTypes.func.isRequired
51 | },
52 |
53 | render: function () {
54 | var isDragging = this.props.isDragging;
55 | var connectDragSource = this.props.connectDragSource;
56 | var text = this.props.text;
57 |
58 | return connectDragSource(
59 |
60 | {text}
61 |
62 | );
63 | }
64 | });
65 |
66 | // Export the wrapped component:
67 | module.exports = DragSource(ItemTypes.CARD, cardSource, collect)(Card);
68 | ```
69 | -------------------
70 | ```js
71 | // Let's make draggable!
72 |
73 | import React, { PropTypes } from 'react';
74 | import { DragSource } from 'react-dnd';
75 | import { ItemTypes } from './Constants';
76 |
77 | /**
78 | * Implements the drag source contract.
79 | */
80 | const cardSource = {
81 | beginDrag(props) {
82 | return {
83 | text: props.text
84 | };
85 | }
86 | };
87 |
88 | /**
89 | * Specifies the props to inject into your component.
90 | */
91 | function collect(connect, monitor) {
92 | return {
93 | connectDragSource: connect.dragSource(),
94 | isDragging: monitor.isDragging()
95 | };
96 | }
97 |
98 | const propTypes = {
99 | text: PropTypes.string.isRequired,
100 |
101 | // Injected by React DnD:
102 | isDragging: PropTypes.bool.isRequired,
103 | connectDragSource: PropTypes.func.isRequired
104 | };
105 |
106 | class Card {
107 | render() {
108 | const { isDragging, connectDragSource, text } = this.props;
109 | return connectDragSource(
110 |
111 | {text}
112 |
113 | );
114 | }
115 | }
116 |
117 | Card.propTypes = propTypes;
118 |
119 | // Export the wrapped component:
120 | export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
121 | ```
122 | -------------------
123 | ```js
124 | // Let's make draggable!
125 |
126 | import React, { PropTypes } from 'react';
127 | import { DragSource } from 'react-dnd';
128 | import { ItemTypes } from './Constants';
129 |
130 | /**
131 | * Implements the drag source contract.
132 | */
133 | const cardSource = {
134 | beginDrag(props) {
135 | return {
136 | text: props.text
137 | };
138 | }
139 | };
140 |
141 | @DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
142 | connectDragSource: connect.dragSource(),
143 | isDragging: monitor.isDragging()
144 | }))
145 | export default class Card {
146 | static propTypes = {
147 | text: PropTypes.string.isRequired,
148 |
149 | // Injected by React DnD:
150 | connectDragSource: PropTypes.func.isRequired,
151 | isDragging: PropTypes.bool.isRequired
152 | };
153 |
154 | render() {
155 | const { isDragging, connectDragSource, text } = this.props;
156 | return connectDragSource(
157 |
158 | {text}
159 |
160 | );
161 | }
162 | }
163 | ```
164 | -------------------
165 |
166 | ## Features
167 |
168 | ### It works with your components
169 |
170 | Instead of providing readymade widgets, React DnD wraps your components and injects props into them. If you use React Router or Flummox, you already know this pattern.
171 |
172 | ### It embraces unidirectional data flow
173 |
174 | React DnD fully embraces the declarative rendering paradigm of React and doesn't mutate the DOM. It is a great complement to Redux and other architectures with the unidirectional data flow. In fact it is built on Redux.
175 |
176 | ### It hides the platform quirks
177 |
178 | HTML5 drag and drop has an awkward API full of pitfalls and browser inconsistencies. React DnD handles them internally for you, so you can focus on developing your application instead of working around the browser bugs.
179 |
180 | ### It is extensible and testable
181 |
182 | React DnD uses the HTML5 drag and drop under the hood, but it also lets you supply a custom “backend”. You can create a custom DnD backend based on the touch events, the mouse events, or something else entirely. For example, a built-in simulation backend lets you test drag and drop interaction of your components in a Node environment.
183 |
184 | ### It is ready for the future
185 |
186 | React DnD does not export mixins, and works equally great with any components, whether they are created using ES6 classes, `createClass` or alternative React frameworks such as Omniscient. Its API shines with ES7 decorators if you like to be on the bleeding edge, but it also feels natural in both ES5 and ES6.
187 |
188 | ## Touch Support
189 |
190 | For touch support, use React DnD with [the touch backend](https://github.com/yahoo/react-dnd-touch-backend) instead of the HTML5 backend.
191 |
192 | ## Non-Goals
193 |
194 | React DnD gives you a set of powerful primitives, but it does not contain any readymade components. It's lower level than [jQuery UI](https://jqueryui.com/) or [interact.js](http://interactjs.io/) and is focused on getting the drag and drop interaction right, leaving its visual aspects such as axis constraints or snapping to you. For example, React DnD doesn't plan to provide a `Sortable` component. Instead it makes it easy for you to build your own, with any rendering customizations that you need.
195 |
196 | ## Support and Contributions
197 |
198 | Issues and potential improvements are discussed on [Github](https://github.com/gaearon/react-dnd/issues).
199 | The [Gitter room](https://gitter.im/gaearon/react-dnd) is another good place to get support.
200 |
201 | ## Thanks
202 |
203 | Big thanks to [BrowserStack](https://www.browserstack.com) for letting the maintainers use their service to debug browser issues.
204 |
205 | ## License
206 |
207 | React DnD is licensed as MIT. Use it as you will.
208 |
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/Board.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import BoardSquare from './BoardSquare';
3 | import Knight from './Knight';
4 | import { DragDropContext } from 'react-dnd';
5 | import HTML5Backend from 'react-dnd-html5-backend';
6 | import './Board.less';
7 |
8 | @DragDropContext(HTML5Backend)
9 | export default class Board extends Component {
10 |
11 | renderSquare(i) {
12 | const x = i % 8;
13 | const y = Math.floor(i / 8);
14 |
15 | return (
16 |
18 |
20 | {this.renderPiece(x, y)}
21 |
22 |
23 | );
24 | }
25 |
26 | renderPiece(x, y) {
27 | const [knightX, knightY] = this.props.knightPosition;
28 | if (x === knightX && y === knightY) {
29 | return ;
30 | }
31 | }
32 |
33 | render() {
34 | const squares = [];
35 | for (let i = 0; i < 64; i++) {
36 | squares.push(this.renderSquare(i));
37 | }
38 |
39 | return (
40 |
41 | {squares}
42 |
43 | );
44 | }
45 | }
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/Board.less:
--------------------------------------------------------------------------------
1 | .Board {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | flex-wrap: wrap;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/BoardSquare.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import Square from './Square';
3 | import { canMoveKnight, moveKnight } from './Game';
4 | import { ItemTypes } from './Constants';
5 | import { DropTarget } from 'react-dnd';
6 |
7 | const squareTarget = {
8 | canDrop(props) {
9 | return canMoveKnight(props.x, props.y);
10 | },
11 |
12 | drop(props) {
13 | moveKnight(props.x, props.y);
14 | }
15 | };
16 |
17 | function collect(connect, monitor) {
18 | return {
19 | connectDropTarget: connect.dropTarget(),
20 | isOver: monitor.isOver(),
21 | canDrop: monitor.canDrop()
22 | };
23 | }
24 |
25 | @DropTarget(ItemTypes.KNIGHT, squareTarget, collect)
26 | export default class BoardSquare extends Component {
27 |
28 | renderOverlay(color) {
29 | return (
30 |
40 | );
41 | }
42 |
43 | render() {
44 | const { x, y, connectDropTarget, isOver, canDrop, children } = this.props;
45 | const black = (x + y) % 2 === 1;
46 |
47 | return connectDropTarget(
48 |
53 |
54 | {children}
55 |
56 | {isOver && !canDrop && this.renderOverlay('red')}
57 | {!isOver && canDrop && this.renderOverlay('yellow')}
58 | {isOver && canDrop && this.renderOverlay('green')}
59 |
60 | );
61 | }
62 | }
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/Constants.js:
--------------------------------------------------------------------------------
1 | export const ItemTypes = {
2 | KNIGHT: 'knight'
3 | };
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/Game.js:
--------------------------------------------------------------------------------
1 | let knightPosition = [1, 7];
2 | let observer = null;
3 |
4 | function emitChange() {
5 | observer(knightPosition);
6 | }
7 |
8 | export function observe(o) {
9 | if (observer) {
10 | throw new Error('Multiple observers not implemented.');
11 | }
12 |
13 | observer = o;
14 | emitChange();
15 |
16 | return () => {
17 | observer = null;
18 | };
19 | }
20 |
21 | export function canMoveKnight(toX, toY) {
22 | const [x, y] = knightPosition;
23 | const dx = toX - x;
24 | const dy = toY - y;
25 |
26 | return (Math.abs(dx) === 2 && Math.abs(dy) === 1) ||
27 | (Math.abs(dx) === 1 && Math.abs(dy) === 2);
28 | }
29 |
30 | export function moveKnight(toX, toY) {
31 | knightPosition = [toX, toY];
32 | emitChange();
33 | }
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/Square.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 |
3 | export default class Square extends Component {
4 |
5 | render() {
6 | const { black } = this.props;
7 | const backgroundColor = black ? 'black' : 'white';
8 | const color = black ? 'white' : 'black';
9 |
10 | return (
11 |
17 | {this.props.children}
18 |
19 | );
20 | }
21 | }
--------------------------------------------------------------------------------
/examples/00 Chessboard/Tutorial App/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Board from './Board';
3 | import { observe } from './Game';
4 |
5 | /**
6 | * Unlike the tutorial, export a component so it can be used on the website.
7 | */
8 | export default class ChessboardTutorialApp extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.unobserve = observe(this.handleChange.bind(this));
12 | }
13 |
14 | handleChange(knightPosition) {
15 | const nextState = { knightPosition };
16 | if (this.state) {
17 | this.setState(nextState);
18 | } else {
19 | this.state = nextState;
20 | }
21 | }
22 |
23 | componentWillUnmount() {
24 | this.unobserve();
25 | }
26 |
27 | render() {
28 | const { knightPosition } = this.state;
29 | return (
30 |
31 |
32 | Browse the Source
33 |
34 |
35 | This is a sample app you'll build as you work through the tutorial .
36 |
37 |
38 | It illustrates creating the drag sources and the drop targets, using the monitors to query the current drag state, and customizing the drag previews.
39 |
40 |
45 |
46 |
47 |
48 | Make sure to check out the tutorial for step-by-step instructions on building it!
49 |
50 |
51 | );
52 | }
53 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Multiple Targets/Box.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import { DragSource } from 'react-dnd';
3 |
4 | const style = {
5 | border: '1px dashed gray',
6 | backgroundColor: 'white',
7 | padding: '0.5rem 1rem',
8 | marginRight: '1.5rem',
9 | marginBottom: '1.5rem',
10 | cursor: 'move',
11 | float: 'left'
12 | };
13 |
14 | const boxSource = {
15 | beginDrag(props) {
16 | return {
17 | name: props.name
18 | };
19 | }
20 | };
21 |
22 | @DragSource(props => props.type, boxSource, (connect, monitor) => ({
23 | connectDragSource: connect.dragSource(),
24 | isDragging: monitor.isDragging()
25 | }))
26 | export default class Box extends Component {
27 |
28 | render() {
29 | const { name, isDropped, isDragging, connectDragSource } = this.props;
30 | const opacity = isDragging ? 0.4 : 1;
31 |
32 | return connectDragSource(
33 |
34 | {isDropped ?
35 | {name} :
36 | name
37 | }
38 |
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Multiple Targets/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { DragDropContext } from 'react-dnd';
3 | import HTML5Backend, { NativeTypes } from 'react-dnd-html5-backend';
4 | import Dustbin from './Dustbin';
5 | import Box from './Box';
6 | import ItemTypes from './ItemTypes';
7 | import update from 'react/lib/update';
8 |
9 | @DragDropContext(HTML5Backend)
10 | export default class Container extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | dustbins: [
15 | { accepts: [ItemTypes.GLASS], lastDroppedItem: null },
16 | { accepts: [ItemTypes.FOOD], lastDroppedItem: null },
17 | { accepts: [ItemTypes.PAPER, ItemTypes.GLASS, NativeTypes.URL], lastDroppedItem: null },
18 | { accepts: [ItemTypes.PAPER, NativeTypes.FILE], lastDroppedItem: null }
19 | ],
20 | boxes: [
21 | { name: 'Bottle', type: ItemTypes.GLASS },
22 | { name: 'Banana', type: ItemTypes.FOOD },
23 | { name: 'Magazine', type: ItemTypes.PAPER }
24 | ],
25 | droppedBoxNames: []
26 | };
27 | }
28 |
29 | isDropped(boxName) {
30 | return this.state.droppedBoxNames.indexOf(boxName) > -1;
31 | }
32 |
33 | render() {
34 | const { boxes, dustbins } = this.state;
35 |
36 | return (
37 |
38 |
39 | {dustbins.map(({ accepts, lastDroppedItem }, index) =>
40 | this.handleDrop(index, item)}
43 | key={index} />
44 | )}
45 |
46 |
47 |
48 | {boxes.map(({ name, type }, index) =>
49 |
53 | )}
54 |
55 |
56 | );
57 | }
58 |
59 | handleDrop(index, item) {
60 | const { name } = item;
61 |
62 | this.setState(update(this.state, {
63 | dustbins: {
64 | [index]: {
65 | lastDroppedItem: {
66 | $set: item
67 | }
68 | }
69 | },
70 | droppedBoxNames: name ? {
71 | $push: [name]
72 | } : {}
73 | }));
74 | }
75 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Multiple Targets/Dustbin.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import { DropTarget } from 'react-dnd';
3 |
4 | const style = {
5 | height: '12rem',
6 | width: '12rem',
7 | marginRight: '1.5rem',
8 | marginBottom: '1.5rem',
9 | color: 'white',
10 | padding: '1rem',
11 | textAlign: 'center',
12 | fontSize: '1rem',
13 | lineHeight: 'normal',
14 | float: 'left'
15 | };
16 |
17 | const dustbinTarget = {
18 | drop(props, monitor) {
19 | props.onDrop(monitor.getItem());
20 | }
21 | };
22 |
23 | @DropTarget(props => props.accepts, dustbinTarget, (connect, monitor) => ({
24 | connectDropTarget: connect.dropTarget(),
25 | isOver: monitor.isOver(),
26 | canDrop: monitor.canDrop()
27 | }))
28 | export default class Dustbin extends Component {
29 |
30 | render() {
31 | const { accepts, isOver, canDrop, connectDropTarget, lastDroppedItem } = this.props;
32 | const isActive = isOver && canDrop;
33 |
34 | let backgroundColor = '#222';
35 | if (isActive) {
36 | backgroundColor = 'darkgreen';
37 | } else if (canDrop) {
38 | backgroundColor = 'darkkhaki';
39 | }
40 |
41 | return connectDropTarget(
42 |
43 | {isActive ?
44 | 'Release to drop' :
45 | 'This dustbin accepts: ' + accepts.join(', ')
46 | }
47 |
48 | {lastDroppedItem &&
49 |
Last dropped: {JSON.stringify(lastDroppedItem)}
50 | }
51 |
52 | );
53 | }
54 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Multiple Targets/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | FOOD: 'food',
3 | GLASS: 'glass',
4 | PAPER: 'paper'
5 | };
--------------------------------------------------------------------------------
/examples/01 Dustbin/Multiple Targets/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class DustbinMultipleTargets extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | This is a slightly more interesting example.
13 |
14 |
15 | It demonstrates how a single drop target may accept multiple types, and how those types may be derived from props.
16 | It also demonstrates the handling of native files and URLs (try dropping them onto the last two dustbins).
17 |
18 |
19 |
20 | );
21 | }
22 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/Box.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | backgroundColor: 'white',
8 | padding: '0.5rem 1rem',
9 | marginRight: '1.5rem',
10 | marginBottom: '1.5rem',
11 | cursor: 'move',
12 | float: 'left'
13 | };
14 |
15 | const boxSource = {
16 | beginDrag(props) {
17 | return {
18 | name: props.name
19 | };
20 | },
21 |
22 | endDrag(props, monitor) {
23 | const item = monitor.getItem();
24 | const dropResult = monitor.getDropResult();
25 |
26 | if (dropResult) {
27 | window.alert( // eslint-disable-line no-alert
28 | `You dropped ${item.name} into ${dropResult.name}!`
29 | );
30 | }
31 | }
32 | };
33 |
34 | @DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
35 | connectDragSource: connect.dragSource(),
36 | isDragging: monitor.isDragging()
37 | }))
38 | export default class Box extends Component {
39 |
40 | render() {
41 | const { isDragging, connectDragSource } = this.props;
42 | const { name } = this.props;
43 | const opacity = isDragging ? '0.4' : '1';
44 |
45 | return (
46 | connectDragSource(
47 |
48 | {name}
49 |
50 | )
51 | );
52 | }
53 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { DragDropContext } from 'react-dnd';
3 | import HTML5Backend from 'react-dnd-html5-backend';
4 | import Dustbin from './Dustbin';
5 | import Box from './Box';
6 |
7 | @DragDropContext(HTML5Backend)
8 | export default class Container extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/Dustbin.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DropTarget } from 'react-dnd';
4 |
5 | const style = {
6 | height: '12rem',
7 | width: '12rem',
8 | marginRight: '1.5rem',
9 | marginBottom: '1.5rem',
10 | color: 'white',
11 | padding: '1rem',
12 | textAlign: 'center',
13 | fontSize: '1rem',
14 | lineHeight: 'normal',
15 | float: 'left'
16 | };
17 |
18 | const boxTarget = {
19 | drop() {
20 | return { name: 'Dustbin' };
21 | }
22 | };
23 |
24 | @DropTarget(ItemTypes.BOX, boxTarget, (connect, monitor) => ({
25 | connectDropTarget: connect.dropTarget(),
26 | isOver: monitor.isOver(),
27 | canDrop: monitor.canDrop()
28 | }))
29 | export default class Dustbin extends Component {
30 |
31 | render() {
32 | const { canDrop, isOver, connectDropTarget } = this.props;
33 | const isActive = canDrop && isOver;
34 |
35 | let backgroundColor = '#222';
36 | if (isActive) {
37 | backgroundColor = 'darkgreen';
38 | } else if (canDrop) {
39 | backgroundColor = 'darkkhaki';
40 | }
41 |
42 | return connectDropTarget(
43 |
44 | {isActive ?
45 | 'Release to drop' :
46 | 'Drag a box here'
47 | }
48 |
49 | );
50 | }
51 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/__tests__/Box-test.js:
--------------------------------------------------------------------------------
1 | import TestUtils from 'react-dom/lib/ReactTestUtils';
2 | import wrapInTestContext from '../../../shared/wrapInTestContext';
3 | import expect from 'expect';
4 | import Box from '../Box';
5 |
6 | describe('Box', () => {
7 | describe('can be tested independently', () => {
8 | it('it is not dragging', (done) => {
9 | // Obtain the reference to the component before React DnD wrapping
10 | const OriginalBox = Box.DecoratedComponent;
11 |
12 | // Stub the React DnD connector functions with an identity function
13 | const identity = x => x;
14 |
15 | // Render with one set of props and test
16 | TestUtils.renderIntoDocument(
17 | , (instance) => {
21 | const div = findSelector(instance.base, 'div');
22 | expect(div.style.opacity).toEqual('1');
23 | done();
24 | }
25 | );
26 | });
27 |
28 | it('it is dragging', (done) => {
29 | // Obtain the reference to the component before React DnD wrapping
30 | const OriginalBox = Box.DecoratedComponent;
31 |
32 | // Stub the React DnD connector functions with an identity function
33 | const identity = x => x;
34 |
35 | // Render with one set of props and test
36 | TestUtils.renderIntoDocument(
37 | , (instance) => {
41 | const div = findSelector(instance.base, 'div');
42 | expect(div.style.opacity).toEqual('0.4');
43 | done();
44 | }
45 | );
46 | });
47 | })
48 |
49 | it('can be tested with the testing backend', (done) => {
50 | // Render with the testing backend
51 |
52 | let box;
53 | const BoxContext = wrapInTestContext(Box, (a) => {
54 | box = a;
55 | });
56 |
57 | TestUtils.renderIntoDocument(
58 | , (instance) => {
59 | let div;
60 |
61 | // Obtain a reference to the backend
62 | const backend = instance.getManager().getBackend();
63 | div = findSelector(instance.base, 'div');
64 | expect(div.style.opacity).toEqual('1');
65 |
66 | // Simulate the dragging state
67 | backend.simulateBeginDrag([box.getHandlerId()]);
68 |
69 | setTimeout(() => {
70 | // Verify that the div changed its opacity
71 | div = findSelector(instance.base, 'div');
72 | expect(div.style.opacity).toEqual('0.4');
73 |
74 | done();
75 | }, 1);
76 | }
77 | );
78 | });
79 | });
80 |
81 | function findSelector(elem, selector) {
82 | if (elem.matches(selector)) {
83 | return elem;
84 | }
85 |
86 | return elem.querySelector(selector);
87 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Single Target/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class DustbinSingleTarget extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | This is the simplest example there is.
13 |
14 |
15 | Drag the boxes below and drop them into the dustbin.
16 | Note that it has a neutral, an active and a hovered state.
17 | The dragged item itself changes opacity while dragged.
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/01 Dustbin/Stress Test/Box.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import { DragSource } from 'react-dnd';
3 |
4 | const style = {
5 | border: '1px dashed gray',
6 | backgroundColor: 'white',
7 | padding: '0.5rem 1rem',
8 | marginRight: '1.5rem',
9 | marginBottom: '1.5rem',
10 | cursor: 'move',
11 | float: 'left'
12 | };
13 |
14 | const boxSource = {
15 | beginDrag(props) {
16 | return {
17 | name: props.name
18 | };
19 | },
20 |
21 | isDragging(props, monitor) {
22 | const item = monitor.getItem();
23 | return props.name === item.name;
24 | }
25 | };
26 |
27 | @DragSource(props => props.type, boxSource, (connect, monitor) => ({
28 | connectDragSource: connect.dragSource(),
29 | isDragging: monitor.isDragging()
30 | }))
31 | export default class Box extends Component {
32 |
33 | render() {
34 | const { name, isDropped, isDragging, connectDragSource } = this.props;
35 | const opacity = isDragging ? 0.4 : 1;
36 |
37 | return connectDragSource(
38 |
39 | {isDropped ?
40 | {name} :
41 | name
42 | }
43 |
44 | );
45 | }
46 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Stress Test/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { DragDropContext } from 'react-dnd';
3 | import HTML5Backend, { NativeTypes } from 'react-dnd-html5-backend';
4 | import Dustbin from './Dustbin';
5 | import Box from './Box';
6 | import ItemTypes from './ItemTypes';
7 | import shuffle from 'lodash/shuffle';
8 | import update from 'react/lib/update';
9 |
10 | @DragDropContext(HTML5Backend)
11 | export default class Container extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | dustbins: [
16 | { accepts: [ItemTypes.GLASS], lastDroppedItem: null },
17 | { accepts: [ItemTypes.FOOD], lastDroppedItem: null },
18 | { accepts: [ItemTypes.PAPER, ItemTypes.GLASS, NativeTypes.URL], lastDroppedItem: null },
19 | { accepts: [ItemTypes.PAPER, NativeTypes.FILE], lastDroppedItem: null }
20 | ],
21 | boxes: [
22 | { name: 'Bottle', type: ItemTypes.GLASS },
23 | { name: 'Banana', type: ItemTypes.FOOD },
24 | { name: 'Magazine', type: ItemTypes.PAPER }
25 | ],
26 | droppedBoxNames: []
27 | };
28 | }
29 |
30 | componentDidMount() {
31 | this.interval = setInterval(() => this.tickTock(), 1000);
32 | }
33 |
34 | tickTock() {
35 | this.setState({
36 | boxes: shuffle(this.state.boxes),
37 | dustbins: shuffle(this.state.dustbins)
38 | });
39 | }
40 |
41 | componentWillUnmount() {
42 | clearInterval(this.interval);
43 | }
44 |
45 | isDropped(boxName) {
46 | return this.state.droppedBoxNames.indexOf(boxName) > -1;
47 | }
48 |
49 | render() {
50 | const { boxes, dustbins } = this.state;
51 |
52 | return (
53 |
54 |
55 | {dustbins.map(({ accepts, lastDroppedItem }, index) =>
56 | this.handleDrop(index, item)}
59 | key={index} />
60 | )}
61 |
62 |
63 |
64 | {boxes.map(({ name, type }, index) =>
65 |
69 | )}
70 |
71 |
72 | );
73 | }
74 |
75 | handleDrop(index, item) {
76 | const { name } = item;
77 |
78 | this.setState(update(this.state, {
79 | dustbins: {
80 | [index]: {
81 | lastDroppedItem: {
82 | $set: item
83 | }
84 | }
85 | },
86 | droppedBoxNames: name ? {
87 | $push: [name]
88 | } : {}
89 | }));
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/examples/01 Dustbin/Stress Test/Dustbin.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import { DropTarget } from 'react-dnd';
3 |
4 | const style = {
5 | height: '12rem',
6 | width: '12rem',
7 | marginRight: '1.5rem',
8 | marginBottom: '1.5rem',
9 | color: 'white',
10 | padding: '1rem',
11 | textAlign: 'center',
12 | fontSize: '1rem',
13 | lineHeight: 'normal',
14 | float: 'left'
15 | };
16 |
17 | const dustbinTarget = {
18 | drop(props, monitor) {
19 | props.onDrop(monitor.getItem());
20 | }
21 | };
22 |
23 | @DropTarget(props => props.accepts, dustbinTarget, (connect, monitor) => ({
24 | connectDropTarget: connect.dropTarget(),
25 | isOver: monitor.isOver(),
26 | canDrop: monitor.canDrop()
27 | }))
28 | export default class Dustbin extends Component {
29 |
30 | render() {
31 | const { accepts, isOver, canDrop, connectDropTarget, lastDroppedItem } = this.props;
32 | const isActive = isOver && canDrop;
33 |
34 | let backgroundColor = '#222';
35 | if (isActive) {
36 | backgroundColor = 'darkgreen';
37 | } else if (canDrop) {
38 | backgroundColor = 'darkkhaki';
39 | }
40 |
41 | return connectDropTarget(
42 |
43 | {isActive ?
44 | 'Release to drop' :
45 | 'This dustbin accepts: ' + accepts.join(', ')
46 | }
47 |
48 | {lastDroppedItem &&
49 |
Last dropped: {JSON.stringify(lastDroppedItem)}
50 | }
51 |
52 | );
53 | }
54 | }
--------------------------------------------------------------------------------
/examples/01 Dustbin/Stress Test/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | FOOD: 'food',
3 | GLASS: 'glass',
4 | PAPER: 'paper'
5 | };
--------------------------------------------------------------------------------
/examples/01 Dustbin/Stress Test/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class DustbinStressTest extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | This example is similar to the previous one, but props of both the drag sources and the drop targets change every second.
13 | It demonstrates that React DnD keeps track of the changing props, and if a component receives the new props, React DnD recalculates the drag and drop state.
14 | It also shows how a custom isDragging
implementation can make the drag source appear as dragged, even if the component that initiated the drag has received new props.
15 |
16 |
17 |
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/Box.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import shouldPureComponentUpdate from './shouldPureComponentUpdate';
3 |
4 | const styles = {
5 | border: '1px dashed gray',
6 | padding: '0.5rem 1rem',
7 | cursor: 'move'
8 | };
9 |
10 | export default class Box extends Component {
11 |
12 | shouldComponentUpdate = shouldPureComponentUpdate;
13 |
14 | render() {
15 | const { title, yellow } = this.props;
16 | const backgroundColor = yellow ? 'yellow' : 'white';
17 |
18 | return (
19 |
20 | {title}
21 |
22 | );
23 | }
24 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/BoxDragPreview.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import shouldPureComponentUpdate from './shouldPureComponentUpdate';
3 | import Box from './Box';
4 |
5 | const styles = {
6 | display: 'inline-block',
7 | transform: 'rotate(-7deg)',
8 | WebkitTransform: 'rotate(-7deg)'
9 | };
10 |
11 | export default class BoxDragPreview extends Component {
12 |
13 | shouldComponentUpdate = shouldPureComponentUpdate;
14 |
15 | constructor(props) {
16 | super(props);
17 | this.tick = this.tick.bind(this);
18 | this.state = {
19 | tickTock: false
20 | };
21 | }
22 |
23 | componentDidMount() {
24 | this.interval = setInterval(this.tick, 500);
25 | }
26 |
27 | componentWillUnmount() {
28 | clearInterval(this.interval);
29 | }
30 |
31 | tick() {
32 | this.setState({
33 | tickTock: !this.state.tickTock
34 | });
35 | }
36 |
37 | render() {
38 | const { title } = this.props;
39 | const { tickTock } = this.state;
40 |
41 | return (
42 |
43 |
45 |
46 | );
47 | }
48 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/Container.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import shouldPureComponentUpdate from './shouldPureComponentUpdate';
3 | import update from 'react/lib/update';
4 | import ItemTypes from './ItemTypes';
5 | import DraggableBox from './DraggableBox';
6 | import snapToGrid from './snapToGrid';
7 | import { DropTarget } from 'react-dnd';
8 |
9 | const styles = {
10 | width: 300,
11 | height: 300,
12 | border: '1px solid black',
13 | position: 'relative'
14 | };
15 |
16 | const boxTarget = {
17 | drop(props, monitor, component) {
18 | const delta = monitor.getDifferenceFromInitialOffset();
19 | const item = monitor.getItem();
20 |
21 | let left = Math.round(item.left + delta.x);
22 | let top = Math.round(item.top + delta.y);
23 | if (props.snapToGrid) {
24 | [left, top] = snapToGrid(left, top);
25 | }
26 |
27 | component.moveBox(item.id, left, top);
28 | }
29 | };
30 |
31 | @DropTarget(ItemTypes.BOX, boxTarget, connect => ({
32 | connectDropTarget: connect.dropTarget()
33 | }))
34 | export default class Container extends Component {
35 | static propTypes = {
36 | connectDropTarget: PropTypes.func.isRequired,
37 | snapToGrid: PropTypes.bool.isRequired
38 | }
39 |
40 | shouldComponentUpdate = shouldPureComponentUpdate;
41 |
42 | constructor(props) {
43 | super(props);
44 | this.state = {
45 | boxes: {
46 | 'a': { top: 20, left: 80, title: 'Drag me around' },
47 | 'b': { top: 180, left: 20, title: 'Drag me too' }
48 | }
49 | };
50 | }
51 |
52 | moveBox(id, left, top) {
53 | this.setState(update(this.state, {
54 | boxes: {
55 | [id]: {
56 | $merge: {
57 | left: left,
58 | top: top
59 | }
60 | }
61 | }
62 | }));
63 | }
64 |
65 | renderBox(item, key) {
66 | return (
67 |
70 | );
71 | }
72 |
73 | render() {
74 | const { connectDropTarget } = this.props;
75 | const { boxes } = this.state;
76 |
77 | return connectDropTarget(
78 |
79 | {Object
80 | .keys(boxes)
81 | .map(key => this.renderBox(boxes[key], key))
82 | }
83 |
84 | );
85 | }
86 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/CustomDragLayer.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import BoxDragPreview from './BoxDragPreview';
4 | import snapToGrid from './snapToGrid';
5 | import { DragLayer } from 'react-dnd';
6 |
7 | const layerStyles = {
8 | position: 'fixed',
9 | pointerEvents: 'none',
10 | zIndex: 100,
11 | left: 0,
12 | top: 0,
13 | width: '100%',
14 | height: '100%'
15 | };
16 |
17 | function getItemStyles(props) {
18 | const { initialOffset, currentOffset } = props;
19 | if (!initialOffset || !currentOffset) {
20 | return {
21 | display: 'none'
22 | };
23 | }
24 |
25 | let { x, y } = currentOffset;
26 |
27 | if (props.snapToGrid) {
28 | x -= initialOffset.x;
29 | y -= initialOffset.y;
30 | [x, y] = snapToGrid(x, y);
31 | x += initialOffset.x;
32 | y += initialOffset.y;
33 | }
34 |
35 | const transform = `translate(${x}px, ${y}px)`;
36 | return {
37 | transform: transform,
38 | WebkitTransform: transform
39 | };
40 | }
41 |
42 | @DragLayer(monitor => ({
43 | item: monitor.getItem(),
44 | itemType: monitor.getItemType(),
45 | initialOffset: monitor.getInitialSourceClientOffset(),
46 | currentOffset: monitor.getSourceClientOffset(),
47 | isDragging: monitor.isDragging()
48 | }))
49 | export default class CustomDragLayer extends Component {
50 |
51 | renderItem(type, item) {
52 | switch (type) {
53 | case ItemTypes.BOX:
54 | return (
55 |
56 | );
57 | default:
58 | return null;
59 | }
60 | }
61 |
62 | render() {
63 | const { item, itemType, isDragging } = this.props;
64 |
65 | if (!isDragging) {
66 | return null;
67 | }
68 |
69 | return (
70 |
71 |
72 | {this.renderItem(itemType, item)}
73 |
74 |
75 | );
76 | }
77 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/DraggableBox.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import shouldPureComponentUpdate from './shouldPureComponentUpdate';
3 | import ItemTypes from './ItemTypes';
4 | import Box from './Box';
5 | import { DragSource } from 'react-dnd';
6 | import { getEmptyImage } from 'react-dnd-html5-backend';
7 |
8 | const boxSource = {
9 | beginDrag(props) {
10 | const { id, title, left, top } = props;
11 | return { id, title, left, top };
12 | }
13 | };
14 |
15 | function getStyles(props) {
16 | const { left, top, isDragging } = props;
17 | const transform = `translate3d(${left}px, ${top}px, 0)`;
18 |
19 | return {
20 | position: 'absolute',
21 | transform: transform,
22 | WebkitTransform: transform,
23 | // IE fallback: hide the real node using CSS when dragging
24 | // because IE will ignore our custom "empty image" drag preview.
25 | opacity: isDragging ? 0 : 1,
26 | height: isDragging ? 0 : ''
27 | };
28 | }
29 |
30 | @DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
31 | connectDragSource: connect.dragSource(),
32 | connectDragPreview: connect.dragPreview(),
33 | isDragging: monitor.isDragging()
34 | }))
35 | export default class DraggableBox extends Component {
36 |
37 | shouldComponentUpdate = shouldPureComponentUpdate;
38 |
39 | componentDidMount() {
40 | // Use empty image as a drag preview so browsers don't draw it
41 | // and we can draw whatever we want on the custom drag layer instead.
42 | this.props.connectDragPreview(getEmptyImage(), {
43 | // IE fallback: specify that we'd rather screenshot the node
44 | // when it already knows it's being dragged so we can hide it with CSS.
45 | captureDraggingState: true
46 | });
47 | }
48 |
49 | render() {
50 | const { title, connectDragSource } = this.props;
51 |
52 | return connectDragSource(
53 |
54 |
55 |
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 | import CustomDragLayer from './CustomDragLayer';
4 | import { DragDropContext } from 'react-dnd';
5 | import HTML5Backend from 'react-dnd-html5-backend';
6 |
7 | @DragDropContext(HTML5Backend)
8 | export default class DragAroundCustomDragLayer extends Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.handleSnapToGridAfterDropChange = this.handleSnapToGridAfterDropChange.bind(this);
13 | this.handleSnapToGridWhileDraggingChange = this.handleSnapToGridWhileDraggingChange.bind(this);
14 |
15 | this.state = {
16 | snapToGridAfterDrop: false,
17 | snapToGridWhileDragging: false
18 | };
19 | }
20 |
21 | render() {
22 | const { snapToGridAfterDrop, snapToGridWhileDragging } = this.state;
23 |
24 | return (
25 |
26 |
27 | Browse the Source
28 |
29 |
30 | The browser APIs provide no way to change the drag preview or its behavior once drag has started.
31 | Libraries such as jQuery UI implement the drag and drop from scratch to work around this, but react-dnd
32 | only supports browser drag and drop “backend” for now, so we have to accept its limitations.
33 |
34 |
35 | We can, however, customize behavior a great deal if we feed the browser an empty image as drag preview.
36 | This library provides a DragLayer
that you can use to implement a fixed layer on top of your app where you'd draw a custom drag preview component.
37 |
38 |
39 | Note that we can draw a completely different component on our drag layer if we wish so. It's not just a screenshot.
40 |
41 |
42 | With this approach, we miss out on default “return” animation when dropping outside the container.
43 | However, we get great flexibility in customizing drag feedback and zero flicker.
44 |
45 |
46 |
47 |
48 |
49 |
52 | Snap to grid while dragging
53 |
54 |
55 |
56 |
59 | Snap to grid after drop
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | handleSnapToGridAfterDropChange() {
67 | this.setState({
68 | snapToGridAfterDrop: !this.state.snapToGridAfterDrop
69 | });
70 | }
71 |
72 | handleSnapToGridWhileDraggingChange() {
73 | this.setState({
74 | snapToGridWhileDragging: !this.state.snapToGridWhileDragging
75 | });
76 | }
77 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/shallowEqual.js:
--------------------------------------------------------------------------------
1 | export default function shallowEqual(objA, objB) {
2 | if (objA === objB) {
3 | return true;
4 | }
5 |
6 | const keysA = Object.keys(objA);
7 | const keysB = Object.keys(objB);
8 |
9 | if (keysA.length !== keysB.length) {
10 | return false;
11 | }
12 |
13 | // Test for A's keys different from B.
14 | const hasOwn = Object.prototype.hasOwnProperty;
15 | for (let i = 0; i < keysA.length; i++) {
16 | if (!hasOwn.call(objB, keysA[i]) ||
17 | objA[keysA[i]] !== objB[keysA[i]]) {
18 | return false;
19 | }
20 |
21 | const valA = objA[keysA[i]];
22 | const valB = objB[keysA[i]];
23 |
24 | if (valA !== valB) {
25 | return false;
26 | }
27 | }
28 |
29 | return true;
30 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/shouldPureComponentUpdate.js:
--------------------------------------------------------------------------------
1 | import shallowEqual from './shallowEqual';
2 |
3 | export default function shouldPureComponentUpdate(nextProps, nextState) {
4 | return !shallowEqual(this.props, nextProps) ||
5 | !shallowEqual(this.state, nextState);
6 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Custom Drag Layer/snapToGrid.js:
--------------------------------------------------------------------------------
1 | export default function snapToGrid(x, y) {
2 | const snappedX = Math.round(x / 32) * 32;
3 | const snappedY = Math.round(y / 32) * 32;
4 |
5 | return [snappedX, snappedY];
6 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Naive/Box.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | position: 'absolute',
7 | border: '1px dashed gray',
8 | backgroundColor: 'white',
9 | padding: '0.5rem 1rem',
10 | cursor: 'move',
11 | };
12 |
13 | const boxSource = {
14 | beginDrag(props) {
15 | const { id, left, top } = props;
16 | return { id, left, top };
17 | }
18 | };
19 |
20 | @DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
21 | connectDragSource: connect.dragSource(),
22 | isDragging: monitor.isDragging()
23 | }))
24 | export default class Box extends Component {
25 |
26 | render() {
27 | const { hideSourceOnDrag, left, top, connectDragSource, isDragging, children } = this.props;
28 | if (isDragging && hideSourceOnDrag) {
29 | return null;
30 | }
31 |
32 | return connectDragSource(
33 |
34 | {children}
35 |
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Naive/Container.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import update from 'react/lib/update';
3 | import ItemTypes from './ItemTypes';
4 | import Box from './Box';
5 | import { DropTarget, DragDropContext } from 'react-dnd';
6 | import HTML5Backend from 'react-dnd-html5-backend';
7 |
8 | const styles = {
9 | width: 300,
10 | height: 300,
11 | border: '1px solid black',
12 | position: 'relative'
13 | };
14 |
15 | const boxTarget = {
16 | drop(props, monitor, component) {
17 | const item = monitor.getItem();
18 | const delta = monitor.getDifferenceFromInitialOffset();
19 | const left = Math.round(item.left + delta.x);
20 | const top = Math.round(item.top + delta.y);
21 |
22 | component.moveBox(item.id, left, top);
23 | }
24 | };
25 |
26 | @DragDropContext(HTML5Backend)
27 | @DropTarget(ItemTypes.BOX, boxTarget, connect => ({
28 | connectDropTarget: connect.dropTarget()
29 | }))
30 | export default class Container extends Component {
31 |
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | boxes: {
36 | 'a': { top: 20, left: 80, title: 'Drag me around' },
37 | 'b': { top: 180, left: 20, title: 'Drag me too' }
38 | }
39 | };
40 | }
41 |
42 | moveBox(id, left, top) {
43 | this.setState(update(this.state, {
44 | boxes: {
45 | [id]: {
46 | $merge: {
47 | left: left,
48 | top: top
49 | }
50 | }
51 | }
52 | }));
53 | }
54 |
55 | render() {
56 | const { hideSourceOnDrag, connectDropTarget } = this.props;
57 | const { boxes} = this.state;
58 |
59 | return connectDropTarget(
60 |
61 | {Object.keys(boxes).map(key => {
62 | const { left, top, title } = boxes[key];
63 | return (
64 |
69 | {title}
70 |
71 | );
72 | })}
73 |
74 | );
75 | }
76 | }
--------------------------------------------------------------------------------
/examples/02 Drag Around/Naive/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/02 Drag Around/Naive/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class DragAroundNaive extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.handleHideSourceClick = this.handleHideSourceClick.bind(this);
8 | this.state = {
9 | hideSourceOnDrag: true
10 | };
11 | }
12 |
13 | handleHideSourceClick() {
14 | this.setState({
15 | hideSourceOnDrag: !this.state.hideSourceOnDrag
16 | });
17 | }
18 |
19 | render() {
20 | const { hideSourceOnDrag } = this.state;
21 |
22 | return (
23 |
24 |
25 | Browse the Source
26 |
27 |
28 | This example naively relies on browser drag and drop implementation without much custom logic.
29 |
30 |
31 | When the box is dragged, we remove its original DOM node by returning null
from render()
and let browser draw the drag preview.
32 | When the is released, we draw it at the new coordinates.
33 | If you try to drag the box outside the container, the browser will animate its return.
34 |
35 |
36 | While this approach works for simple cases, it flickers on drop.
37 | This happens because the browser removes the drag preview before we have a chance to make the dragged item visible.
38 | This might not be a problem if you dim the original item instead of hiding it, but it's clearly visible otherwise.
39 |
40 |
41 | If we want to add custom logic such as snapping to grid or bounds checking, we can only do this on drop.
42 | There is no way for us to control what happens to dragged preview once the browser has drawn it.
43 | Check out the custom rendering example if you'd rather trade more control for some more work.
44 |
45 |
46 |
47 |
48 |
51 | Hide the source item while dragging
52 |
53 |
54 |
55 | );
56 | }
57 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drag Sources/Colors.js:
--------------------------------------------------------------------------------
1 | export default {
2 | YELLOW: 'yellow',
3 | BLUE: 'blue'
4 | };
--------------------------------------------------------------------------------
/examples/03 Nesting/Drag Sources/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import SourceBox from './SourceBox';
3 | import TargetBox from './TargetBox';
4 | import Colors from './Colors';
5 | import { DragDropContext } from 'react-dnd';
6 | import HTML5Backend from 'react-dnd-html5-backend';
7 |
8 | @DragDropContext(HTML5Backend)
9 | export default class Container extends Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drag Sources/SourceBox.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import Colors from './Colors';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | padding: '0.5rem',
8 | margin: '0.5rem',
9 | };
10 |
11 | const ColorSource = {
12 | canDrag(props) {
13 | return !props.forbidDrag;
14 | },
15 |
16 | beginDrag() {
17 | return { };
18 | }
19 | };
20 |
21 | @DragSource(props => props.color, ColorSource, (connect, monitor) => ({
22 | connectDragSource: connect.dragSource(),
23 | isDragging: monitor.isDragging()
24 | }))
25 | class SourceBox extends Component {
26 |
27 | render() {
28 | const { color, children, isDragging, connectDragSource, forbidDrag, onToggleForbidDrag } = this.props;
29 | const opacity = isDragging ? 0.4 : 1;
30 |
31 | let backgroundColor;
32 | switch (color) {
33 | case Colors.YELLOW:
34 | backgroundColor = 'lightgoldenrodyellow';
35 | break;
36 | case Colors.BLUE:
37 | backgroundColor = 'lightblue';
38 | break;
39 | default:
40 | break;
41 | }
42 |
43 | return connectDragSource(
44 |
50 |
53 | Forbid drag
54 |
55 | {children}
56 |
57 | );
58 | }
59 | }
60 |
61 | export default class StatefulSourceBox extends Component {
62 | constructor(props) {
63 | super(props);
64 | this.state = {
65 | forbidDrag: false
66 | };
67 | }
68 |
69 | render() {
70 | return (
71 | this.handleToggleForbidDrag()} />
74 | );
75 | }
76 |
77 | handleToggleForbidDrag() {
78 | this.setState({
79 | forbidDrag: !this.state.forbidDrag
80 | });
81 | }
82 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drag Sources/TargetBox.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import { DropTarget } from 'react-dnd';
3 | import Colors from './Colors';
4 |
5 | const style = {
6 | border: '1px solid gray',
7 | height: '15rem',
8 | width: '15rem',
9 | padding: '2rem',
10 | textAlign: 'center'
11 | };
12 |
13 | const ColorTarget = {
14 | drop(props, monitor) {
15 | props.onDrop(monitor.getItemType());
16 | }
17 | };
18 |
19 | @DropTarget([Colors.YELLOW, Colors.BLUE], ColorTarget, (connect, monitor) => ({
20 | connectDropTarget: connect.dropTarget(),
21 | isOver: monitor.isOver(),
22 | canDrop: monitor.canDrop(),
23 | draggingColor: monitor.getItemType()
24 | }))
25 | class TargetBox extends Component {
26 |
27 | render() {
28 | const { canDrop, isOver, draggingColor, lastDroppedColor, connectDropTarget } = this.props;
29 | const opacity = isOver ? 1 : 0.7;
30 |
31 | let backgroundColor = '#fff';
32 | switch (draggingColor) {
33 | case Colors.BLUE:
34 | backgroundColor = 'lightblue';
35 | break;
36 | case Colors.YELLOW:
37 | backgroundColor = 'lightgoldenrodyellow';
38 | break;
39 | default:
40 | break;
41 | }
42 |
43 | return connectDropTarget(
44 |
45 |
46 |
Drop here.
47 |
48 | {!canDrop && lastDroppedColor &&
49 |
Last dropped: {lastDroppedColor}
50 | }
51 |
52 | );
53 | }
54 | }
55 |
56 | export default class StatefulTargetBox extends Component {
57 | constructor(props) {
58 | super(props);
59 | this.state = { lastDroppedColor: null };
60 | }
61 |
62 | render() {
63 | return (
64 | this.handleDrop(color)} />
67 | );
68 | }
69 |
70 | handleDrop(color) {
71 | this.setState({
72 | lastDroppedColor: color
73 | });
74 | }
75 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drag Sources/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class NestingDragSources extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | You can nest the drag sources in one another.
13 | If a nested drag source returns false
from canDrag
, its parent will be asked, until a draggable source is found and activated.
14 | Only the activated drag source will have its beginDrag()
and endDrag()
called.
15 |
16 |
17 |
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drop Targets/Box.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | display: 'inline-block',
7 | border: '1px dashed gray',
8 | padding: '0.5rem 1rem',
9 | backgroundColor: 'white',
10 | cursor: 'move'
11 | };
12 |
13 | const boxSource = {
14 | beginDrag() {
15 | return {};
16 | }
17 | };
18 |
19 | @DragSource(ItemTypes.BOX, boxSource, (connect) => ({
20 | connectDragSource: connect.dragSource()
21 | }))
22 | export default class Box extends Component {
23 |
24 | render() {
25 | const { connectDragSource } = this.props;
26 |
27 | return connectDragSource(
28 |
29 | Drag me
30 |
31 | );
32 | }
33 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drop Targets/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Dustbin from './Dustbin';
3 | import Box from './Box';
4 | import { DragDropContext } from 'react-dnd';
5 | import HTML5Backend from 'react-dnd-html5-backend';
6 |
7 | @DragDropContext(HTML5Backend)
8 | export default class Container extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drop Targets/Dustbin.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DropTarget } from 'react-dnd';
4 |
5 | function getStyle(backgroundColor) {
6 | return {
7 | border: '1px solid rgba(0,0,0,0.2)',
8 | minHeight: '8rem',
9 | minWidth: '8rem',
10 | color: 'white',
11 | backgroundColor: backgroundColor,
12 | padding: '2rem',
13 | paddingTop: '1rem',
14 | margin: '1rem',
15 | textAlign: 'center',
16 | float: 'left',
17 | fontSize: '1rem'
18 | };
19 | }
20 |
21 | const boxTarget = {
22 | drop(props, monitor, component) {
23 | const hasDroppedOnChild = monitor.didDrop();
24 | if (hasDroppedOnChild && !props.greedy) {
25 | return;
26 | }
27 |
28 | component.setState({
29 | hasDropped: true,
30 | hasDroppedOnChild: hasDroppedOnChild
31 | });
32 | }
33 | };
34 |
35 | @DropTarget(ItemTypes.BOX, boxTarget, (connect, monitor) => ({
36 | connectDropTarget: connect.dropTarget(),
37 | isOver: monitor.isOver(),
38 | isOverCurrent: monitor.isOver({ shallow: true })
39 | }))
40 | export default class Dustbin extends Component {
41 |
42 | constructor(props) {
43 | super(props);
44 | this.state = {
45 | hasDropped: false,
46 | hasDroppedOnChild: false
47 | };
48 | }
49 |
50 | render() {
51 | const { greedy, isOver, isOverCurrent, connectDropTarget, children } = this.props;
52 | const { hasDropped, hasDroppedOnChild } = this.state;
53 |
54 | const text = greedy ? 'greedy' : 'not greedy';
55 | let backgroundColor = 'rgba(0, 0, 0, .5)';
56 |
57 | if (isOverCurrent || isOver && greedy) {
58 | backgroundColor = 'darkgreen';
59 | }
60 |
61 | return connectDropTarget(
62 |
63 | {text}
64 |
65 | {hasDropped &&
66 |
dropped {hasDroppedOnChild && ' on child'}
67 | }
68 |
69 |
70 | {children}
71 |
72 |
73 | );
74 | }
75 | }
--------------------------------------------------------------------------------
/examples/03 Nesting/Drop Targets/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/03 Nesting/Drop Targets/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class NestingDropTargets extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | Drop targets can, too, be nested in one another.
13 | Unlike the drag sources, several drop targets may react to the same item being dragged.
14 | React DnD by design offers no means of stopping propagation.
15 | Instead, the drop targets may compare monitor.isOver()
and monitor.isOver({'{'} shallow: false {'}'})
to learn if just them, or their nested targets, are being hovered.
16 | They may also check monitor.didDrop()
and monitor.getDropResult()
to learn if a nested target has already handled the drop, and even return a different drop result.
17 |
18 |
19 |
20 | );
21 | }
22 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Cancel on Drop Outside/Card.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource, DropTarget } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | padding: '0.5rem 1rem',
8 | marginBottom: '.5rem',
9 | backgroundColor: 'white',
10 | cursor: 'move'
11 | };
12 |
13 | const cardSource = {
14 | beginDrag(props) {
15 | return {
16 | id: props.id,
17 | originalIndex: props.findCard(props.id).index
18 | };
19 | },
20 |
21 | endDrag(props, monitor) {
22 | const { id: droppedId, originalIndex } = monitor.getItem();
23 | const didDrop = monitor.didDrop();
24 |
25 | if (!didDrop) {
26 | props.moveCard(droppedId, originalIndex);
27 | }
28 | }
29 | };
30 |
31 | const cardTarget = {
32 | canDrop() {
33 | return false;
34 | },
35 |
36 | hover(props, monitor) {
37 | const { id: draggedId } = monitor.getItem();
38 | const { id: overId } = props;
39 |
40 | if (draggedId !== overId) {
41 | const { index: overIndex } = props.findCard(overId);
42 | props.moveCard(draggedId, overIndex);
43 | }
44 | }
45 | };
46 |
47 | @DropTarget(ItemTypes.CARD, cardTarget, connect => ({
48 | connectDropTarget: connect.dropTarget()
49 | }))
50 | @DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
51 | connectDragSource: connect.dragSource(),
52 | isDragging: monitor.isDragging()
53 | }))
54 | export default class Card extends Component {
55 |
56 | render() {
57 | const { text, isDragging, connectDragSource, connectDropTarget } = this.props;
58 | const opacity = isDragging ? 0 : 1;
59 |
60 | return connectDragSource(connectDropTarget(
61 |
62 | {text}
63 |
64 | ));
65 | }
66 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Cancel on Drop Outside/Container.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import update from 'react/lib/update';
3 | import Card from './Card';
4 | import { DropTarget, DragDropContext } from 'react-dnd';
5 | import HTML5Backend from 'react-dnd-html5-backend';
6 | import ItemTypes from './ItemTypes';
7 |
8 | const style = {
9 | width: 400
10 | };
11 |
12 | const cardTarget = {
13 | drop() {
14 | }
15 | };
16 |
17 | @DragDropContext(HTML5Backend)
18 | @DropTarget(ItemTypes.CARD, cardTarget, connect => ({
19 | connectDropTarget: connect.dropTarget()
20 | }))
21 | export default class Container extends Component {
22 |
23 | constructor(props) {
24 | super(props);
25 | this.moveCard = this.moveCard.bind(this);
26 | this.findCard = this.findCard.bind(this);
27 | this.state = {
28 | cards: [{
29 | id: 1,
30 | text: 'Write a cool JS library'
31 | }, {
32 | id: 2,
33 | text: 'Make it generic enough'
34 | }, {
35 | id: 3,
36 | text: 'Write README'
37 | }, {
38 | id: 4,
39 | text: 'Create some examples'
40 | }, {
41 | id: 5,
42 | text: 'Spam in Twitter and IRC to promote it'
43 | }, {
44 | id: 6,
45 | text: '???'
46 | }, {
47 | id: 7,
48 | text: 'PROFIT'
49 | }]
50 | };
51 | }
52 |
53 | moveCard(id, atIndex) {
54 | const { card, index } = this.findCard(id);
55 | this.setState(update(this.state, {
56 | cards: {
57 | $splice: [
58 | [index, 1],
59 | [atIndex, 0, card]
60 | ]
61 | }
62 | }));
63 | }
64 |
65 | findCard(id) {
66 | const { cards } = this.state;
67 | const card = cards.filter(c => c.id === id)[0];
68 |
69 | return {
70 | card,
71 | index: cards.indexOf(card)
72 | };
73 | }
74 |
75 | render() {
76 | const { connectDropTarget } = this.props;
77 | const { cards } = this.state;
78 |
79 | return connectDropTarget(
80 |
81 | {cards.map(card => {
82 | return (
83 |
88 | );
89 | })}
90 |
91 | );
92 | }
93 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Cancel on Drop Outside/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | CARD: 'card'
3 | };
--------------------------------------------------------------------------------
/examples/04 Sortable/Cancel on Drop Outside/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class SortableCancelOnDropOutside extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | Because you write the logic instead of using the readymade components, you can tweak the behavior to the one your app needs.
13 | In this example, instead of moving the card inside the drop target's drop()
handler, we do it inside the drag source's endDrag()
handler. This let us check monitor.didDrop()
and revert the drag operation if the card was dropped outside its container.
14 |
15 |
16 |
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Simple/Card.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import { findDOMNode } from 'react-dom';
3 | import ItemTypes from './ItemTypes';
4 | import { DragSource, DropTarget } from 'react-dnd';
5 |
6 | const style = {
7 | border: '1px dashed gray',
8 | padding: '0.5rem 1rem',
9 | marginBottom: '.5rem',
10 | backgroundColor: 'white',
11 | cursor: 'move'
12 | };
13 |
14 | const cardSource = {
15 | beginDrag(props) {
16 | return {
17 | id: props.id,
18 | index: props.index
19 | };
20 | }
21 | };
22 |
23 | const cardTarget = {
24 | hover(props, monitor, component) {
25 | const dragIndex = monitor.getItem().index;
26 | const hoverIndex = props.index;
27 |
28 | // Don't replace items with themselves
29 | if (dragIndex === hoverIndex) {
30 | return;
31 | }
32 |
33 | // Determine rectangle on screen
34 | const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
35 |
36 | // Get vertical middle
37 | const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
38 |
39 | // Determine mouse position
40 | const clientOffset = monitor.getClientOffset();
41 |
42 | // Get pixels to the top
43 | const hoverClientY = clientOffset.y - hoverBoundingRect.top;
44 |
45 | // Only perform the move when the mouse has crossed half of the items height
46 | // When dragging downwards, only move when the cursor is below 50%
47 | // When dragging upwards, only move when the cursor is above 50%
48 |
49 | // Dragging downwards
50 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
51 | return;
52 | }
53 |
54 | // Dragging upwards
55 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
56 | return;
57 | }
58 |
59 | // Time to actually perform the action
60 | props.moveCard(dragIndex, hoverIndex);
61 |
62 | // Note: we're mutating the monitor item here!
63 | // Generally it's better to avoid mutations,
64 | // but it's good here for the sake of performance
65 | // to avoid expensive index searches.
66 | monitor.getItem().index = hoverIndex;
67 | }
68 | };
69 |
70 | @DropTarget(ItemTypes.CARD, cardTarget, connect => ({
71 | connectDropTarget: connect.dropTarget()
72 | }))
73 | @DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
74 | connectDragSource: connect.dragSource(),
75 | isDragging: monitor.isDragging()
76 | }))
77 | export default class Card extends Component {
78 | static propTypes = {
79 | connectDragSource: PropTypes.func.isRequired,
80 | connectDropTarget: PropTypes.func.isRequired,
81 | index: PropTypes.number.isRequired,
82 | isDragging: PropTypes.bool.isRequired,
83 | id: PropTypes.any.isRequired,
84 | text: PropTypes.string.isRequired,
85 | moveCard: PropTypes.func.isRequired
86 | };
87 |
88 | render() {
89 | const { text, isDragging, connectDragSource, connectDropTarget } = this.props;
90 | const opacity = isDragging ? 0 : 1;
91 |
92 | return connectDragSource(connectDropTarget(
93 |
94 | {text}
95 |
96 | ));
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/examples/04 Sortable/Simple/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import update from 'react/lib/update';
3 | import Card from './Card';
4 | import { DragDropContext } from 'react-dnd';
5 | import HTML5Backend from 'react-dnd-html5-backend';
6 |
7 | const style = {
8 | width: 400
9 | };
10 |
11 | @DragDropContext(HTML5Backend)
12 | export default class Container extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.moveCard = this.moveCard.bind(this);
16 | this.state = {
17 | cards: [{
18 | id: 1,
19 | text: 'Write a cool JS library'
20 | }, {
21 | id: 2,
22 | text: 'Make it generic enough'
23 | }, {
24 | id: 3,
25 | text: 'Write README'
26 | }, {
27 | id: 4,
28 | text: 'Create some examples'
29 | }, {
30 | id: 5,
31 | text: 'Spam in Twitter and IRC to promote it (note that this element is taller than the others)'
32 | }, {
33 | id: 6,
34 | text: '???'
35 | }, {
36 | id: 7,
37 | text: 'PROFIT'
38 | }]
39 | };
40 | }
41 |
42 | moveCard(dragIndex, hoverIndex) {
43 | const { cards } = this.state;
44 | const dragCard = cards[dragIndex];
45 |
46 | this.setState(update(this.state, {
47 | cards: {
48 | $splice: [
49 | [dragIndex, 1],
50 | [hoverIndex, 0, dragCard]
51 | ]
52 | }
53 | }));
54 | }
55 |
56 | render() {
57 | const { cards } = this.state;
58 |
59 | return (
60 |
61 | {cards.map((card, i) => {
62 | return (
63 |
68 | );
69 | })}
70 |
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/examples/04 Sortable/Simple/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | CARD: 'card'
3 | };
--------------------------------------------------------------------------------
/examples/04 Sortable/Simple/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class SortableSimple extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | It is easy to implement a sortable interface with React DnD. Just make the same component both a drag source and a drop target, and reorder the data in the hover
handler.
13 |
14 |
15 |
16 | );
17 | }
18 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Stress Test/Card.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource, DropTarget } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | padding: '0.5rem 1rem',
8 | marginBottom: '.5rem',
9 | backgroundColor: 'white',
10 | cursor: 'move'
11 | };
12 |
13 | const cardSource = {
14 | beginDrag(props) {
15 | return { id: props.id };
16 | }
17 | };
18 |
19 | const cardTarget = {
20 | hover(props, monitor) {
21 | const draggedId = monitor.getItem().id;
22 |
23 | if (draggedId !== props.id) {
24 | props.moveCard(draggedId, props.id);
25 | }
26 | }
27 | };
28 |
29 | @DropTarget(ItemTypes.CARD, cardTarget, connect => ({
30 | connectDropTarget: connect.dropTarget()
31 | }))
32 | @DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
33 | connectDragSource: connect.dragSource(),
34 | isDragging: monitor.isDragging()
35 | }))
36 | export default class Card extends Component {
37 |
38 | render() {
39 | const { text, isDragging, connectDragSource, connectDropTarget } = this.props;
40 | const opacity = isDragging ? 0 : 1;
41 |
42 | return connectDragSource(connectDropTarget(
43 |
44 | {text}
45 |
46 | ));
47 | }
48 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Stress Test/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import update from 'react/lib/update';
3 | import { name } from 'faker';
4 | import Card from './Card';
5 | import { DragDropContext } from 'react-dnd';
6 | import HTML5Backend from 'react-dnd-html5-backend';
7 |
8 | const style = {
9 | width: 400
10 | };
11 |
12 | @DragDropContext(HTML5Backend)
13 | export default class Container extends Component {
14 | constructor(props) {
15 | super(props);
16 |
17 | this.moveCard = this.moveCard.bind(this);
18 | this.drawFrame = this.drawFrame.bind(this);
19 |
20 | const cardsById = {};
21 | const cardsByIndex = [];
22 |
23 | for (let i = 0; i < 1000; i++) {
24 | const card = { id: i, text: name.findName() };
25 | cardsById[card.id] = card;
26 | cardsByIndex[i] = card;
27 | }
28 |
29 | this.state = {
30 | cardsById,
31 | cardsByIndex
32 | };
33 | }
34 |
35 | moveCard(id, afterId) {
36 | const { cardsById, cardsByIndex } = this.state;
37 |
38 | const card = cardsById[id];
39 | const afterCard = cardsById[afterId];
40 |
41 | const cardIndex = cardsByIndex.indexOf(card);
42 | const afterIndex = cardsByIndex.indexOf(afterCard);
43 |
44 | this.scheduleUpdate({
45 | cardsByIndex: {
46 | $splice: [
47 | [cardIndex, 1],
48 | [afterIndex, 0, card]
49 | ]
50 | }
51 | });
52 | }
53 |
54 | componentWillUnmount() {
55 | cancelAnimationFrame(this.requestedFrame);
56 | }
57 |
58 | scheduleUpdate(updateFn) {
59 | this.pendingUpdateFn = updateFn;
60 |
61 | if (!this.requestedFrame) {
62 | this.requestedFrame = requestAnimationFrame(this.drawFrame);
63 | }
64 | }
65 |
66 | drawFrame() {
67 | const nextState = update(this.state, this.pendingUpdateFn);
68 | this.setState(nextState);
69 |
70 | this.pendingUpdateFn = null;
71 | this.requestedFrame = null;
72 | }
73 |
74 | render() {
75 | const { cardsByIndex } = this.state;
76 |
77 | return (
78 |
79 | {cardsByIndex.map(card => {
80 | return (
81 |
85 | );
86 | })}
87 |
88 | );
89 | }
90 | }
--------------------------------------------------------------------------------
/examples/04 Sortable/Stress Test/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | CARD: 'card'
3 | };
--------------------------------------------------------------------------------
/examples/04 Sortable/Stress Test/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class SortableStressTest extends Component {
5 | constructor(props) {
6 | super(props);
7 | // Avoid rendering on server because the big data list is generated
8 | this.state = { shouldRender: false };
9 | }
10 |
11 | componentDidMount() {
12 | // Won't fire on server.
13 | this.setState({ shouldRender: true }); // eslint-disable-line react/no-did-mount-set-state
14 | }
15 |
16 | render() {
17 | const { shouldRender } = this.state;
18 |
19 | return (
20 |
21 |
22 | Browse the Source
23 |
24 |
25 | How many items can React DnD handle at the same time?
26 | There are a thousand items in this list.
27 | With some optimizations like updating the state inside a requestAnimationFrame
callback, it can handle a few thousand items without lagging.
28 | After that, you're better off using virtual lists like fixed-data-table .
29 | Luckily, React DnD is designed to work great with any virtual React data list components because it doesn't keep any state in the DOM.
30 |
31 |
32 | This example does not scroll automatically but you can add the scrolling with a parent drop target that compares component.getBoundingClientRect()
with monitor.getClientOffset()
inside its hover
handler.
33 | In fact, you are welcome to contribute this functionality to this example!
34 |
35 | {shouldRender &&
}
36 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/05 Customize/Drop Effects/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { DragDropContext } from 'react-dnd';
3 | import HTML5Backend from 'react-dnd-html5-backend';
4 | import SourceBox from './SourceBox';
5 | import TargetBox from './TargetBox';
6 |
7 | @DragDropContext(HTML5Backend)
8 | export default class Container extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
--------------------------------------------------------------------------------
/examples/05 Customize/Drop Effects/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/05 Customize/Drop Effects/SourceBox.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | backgroundColor: 'white',
8 | padding: '0.5rem 1rem',
9 | marginRight: '1rem',
10 | marginBottom: '1rem',
11 | cursor: 'move'
12 | };
13 |
14 | const boxSource = {
15 | beginDrag() {
16 | return {};
17 | }
18 | };
19 |
20 | @DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
21 | connectDragSource: connect.dragSource(),
22 | isDragging: monitor.isDragging()
23 | }))
24 | export default class SourceBox extends Component {
25 |
26 | render() {
27 | const { isDragging, connectDragSource, showCopyIcon } = this.props;
28 | const opacity = isDragging ? 0.4 : 1;
29 | const dropEffect = showCopyIcon ? 'copy' : 'move';
30 |
31 | return connectDragSource(
32 |
33 | When I am over a drop zone, I have {showCopyIcon ? 'copy' : 'no'} icon.
34 |
,
35 | { dropEffect }
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/examples/05 Customize/Drop Effects/TargetBox.js:
--------------------------------------------------------------------------------
1 | import { PropTypes, Component } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DropTarget } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px solid gray',
7 | height: '15rem',
8 | width: '15rem',
9 | padding: '2rem',
10 | textAlign: 'center'
11 | };
12 |
13 | const boxTarget = {
14 | drop() {
15 | }
16 | };
17 |
18 | @DropTarget(ItemTypes.BOX, boxTarget, (connect, monitor) => ({
19 | connectDropTarget: connect.dropTarget(),
20 | isOver: monitor.isOver(),
21 | canDrop: monitor.canDrop()
22 | }))
23 | export default class TargetBox extends Component {
24 |
25 | render() {
26 | const { canDrop, isOver, connectDropTarget } = this.props;
27 | const isActive = canDrop && isOver;
28 |
29 | return connectDropTarget(
30 |
31 | {isActive ?
32 | 'Release to drop' :
33 | 'Drag item here'
34 | }
35 |
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/examples/05 Customize/Drop Effects/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class CustomizeDropEffects extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | Some browsers let you specify the “drop effects” for the draggable items.
13 | In the compatible browsers, you will see a “copy” icon when you drag the first box over the drop zone.
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/05 Customize/Handles and Previews/BoxWithHandle.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes } from 'react';
2 | import ItemTypes from './ItemTypes';
3 | import { DragSource } from 'react-dnd';
4 |
5 | const style = {
6 | border: '1px dashed gray',
7 | padding: '0.5rem 1rem',
8 | marginBottom: '.5rem',
9 | backgroundColor: 'white',
10 | width: '20rem'
11 | };
12 |
13 | const handleStyle = {
14 | backgroundColor: 'green',
15 | width: '1rem',
16 | height: '1rem',
17 | display: 'inline-block',
18 | marginRight: '0.75rem',
19 | cursor: 'move'
20 | };
21 |
22 | const boxSource = {
23 | beginDrag() {
24 | return {};
25 | }
26 | };
27 |
28 | @DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
29 | connectDragSource: connect.dragSource(),
30 | connectDragPreview: connect.dragPreview(),
31 | isDragging: monitor.isDragging()
32 | }))
33 | export default class BoxWithHandle extends Component {
34 |
35 | render() {
36 | const { isDragging, connectDragSource, connectDragPreview } = this.props;
37 | const opacity = isDragging ? 0.4 : 1;
38 |
39 | return connectDragPreview(
40 |
41 |
42 | {connectDragSource(
43 |
44 | )}
45 |
46 | Drag me by the handle
47 |
48 | );
49 | }
50 | }
--------------------------------------------------------------------------------
/examples/05 Customize/Handles and Previews/Container.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { DragDropContext } from 'react-dnd';
3 | import HTML5Backend from 'react-dnd-html5-backend';
4 | import BoxWithImage from './BoxWithImage';
5 | import BoxWithHandle from './BoxWithHandle';
6 |
7 | @DragDropContext(HTML5Backend)
8 | export default class Container extends Component {
9 | render() {
10 | return (
11 |
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/examples/05 Customize/Handles and Previews/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box'
3 | };
--------------------------------------------------------------------------------
/examples/05 Customize/Handles and Previews/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Container from './Container';
3 |
4 | export default class CustomizeHandlesAndPreviews extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | Browse the Source
10 |
11 |
12 | React DnD lets you choose the draggable node, as well as the drag preview node in your component's render
function.
13 | You may also use an Image
instance that you created programmatically once it has loaded.
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | The examples are written using [ES7](http://gaearon.github.io/react-dnd/docs-faq.html#what-is-the-syntax-i-see-in-the-es7-code-examples-) and use [decorators](https://github.com/wycats/javascript-decorators). You don't have to use the decorators for React DnD, they are completely optional. [The tutorial](http://gaearon.github.io/react-dnd/docs-tutorial.html) should give you a good idea about using React DnD in ES5, ES6, or ES7.
2 |
3 | Check out the [hosted versions of this examples](http://gaearon.github.io/react-dnd/examples-chessboard-tutorial-app.html). To run them locally, clone the repo, and run `npm install && npm start`. It will take a while to start, but after the site is built, you can access the examples by opening [http://localhost:8080/](http://localhost:8080/) and clicking “Examples” in the navigation bar.
4 |
--------------------------------------------------------------------------------
/examples/shared/wrapInTestContext.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'preact';
2 | import TestBackend from 'react-dnd-test-backend';
3 | import { DragDropContext } from 'react-dnd';
4 |
5 | export default function wrapInTestContext(DecoratedComponent, callback) {
6 | class TestStub extends Component {
7 | render() {
8 | return ;
9 | }
10 |
11 | setRef = (decoratedComponent) => {
12 | this.decoratedComponent = decoratedComponent;
13 | }
14 |
15 | componentDidMount() {
16 | callback && callback(this.decoratedComponent);
17 | }
18 | }
19 |
20 | return DragDropContext(TestBackend)(TestStub);
21 | }
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | browsers: ['Chrome'],
7 | singleRun: true,
8 | frameworks: ['mocha'],
9 | files: [
10 | 'tests.webpack.js'
11 | ],
12 | preprocessors: {
13 | 'tests.webpack.js': ['webpack', 'sourcemap']
14 | },
15 | reporters: ['dots'],
16 | webpack: {
17 | devtool: 'inline-source-map',
18 | module: {
19 | loaders: [
20 | { test: /\.js$/, loaders: ['babel'], exclude: /node_modules/ }
21 | ]
22 | },
23 | resolve: {
24 | alias: {
25 | 'react-dnd/modules': path.join(__dirname, './src'),
26 | 'react-dnd': path.join(__dirname, './src'),
27 | 'react-dom/lib/ReactTestUtils': path.join(__dirname, 'test-utils.js'),
28 | 'react': 'preact',
29 | }
30 | },
31 | plugins: [
32 | new webpack.ProvidePlugin({
33 | preact: 'preact'
34 | }),
35 | ]
36 | },
37 | webpackServer: {
38 | noInfo: true
39 | }
40 | });
41 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preact-dnd",
3 | "version": "2.1.6",
4 | "description": "Drag and Drop for Preact (port from React DnD)",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "start": "./scripts/startSiteDevServer.sh",
8 | "build-site": "./scripts/buildStaticSite.sh",
9 | "publish-site": "./scripts/publishStaticSite.sh",
10 | "build": "./scripts/build.sh",
11 | "lint": "eslint .",
12 | "test": "karma start",
13 | "prepublish": "npm run build && npm test"
14 | },
15 | "files": [
16 | "lib/"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/NekR/preact-dnd/"
21 | },
22 | "keywords": [
23 | "react",
24 | "reactjs",
25 | "file",
26 | "drag",
27 | "drop",
28 | "html5",
29 | "draggable",
30 | "droppable",
31 | "drag-and-drop",
32 | "dnd",
33 | "javascript",
34 | "preact-component",
35 | "hoc"
36 | ],
37 | "author": "Dan Abramov (http://github.com/gaearon)",
38 | "license": "MIT",
39 | "bugs": {
40 | "url": "https://github.com/NekR/preact-dnd/issues"
41 | },
42 | "homepage": "https://github.com/NekR/preact-dnd/",
43 | "dependencies": {
44 | "disposables": "^1.0.1",
45 | "dnd-core": "^2.0.1",
46 | "hoist-non-react-statics": "^1.2.0",
47 | "invariant": "^2.1.0",
48 | "lodash": "^4.2.0"
49 | },
50 | "devDependencies": {
51 | "animation-frame": "^0.2.4",
52 | "autoprefixer": "^5.0.0",
53 | "babel-core": "^6.21.0",
54 | "babel-loader": "^6.2.10",
55 | "babel-plugin-transform-class-properties": "^6.24.1",
56 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
57 | "babel-plugin-transform-react-jsx": "^6.24.1",
58 | "babel-preset-es2015": "^6.24.1",
59 | "babel-preset-stage-0": "^6.24.1",
60 | "css-loader": "^0.9.1",
61 | "eslint": "^1.6.0",
62 | "eslint-config-airbnb": "^0.1.0",
63 | "eslint-plugin-react": "^3.5.1",
64 | "expect": "^1.6.0",
65 | "extract-text-webpack-plugin": "^0.3.8",
66 | "faker": "^2.1.3",
67 | "file-loader": "^0.8.1",
68 | "glob": "^4.3.5",
69 | "html-loader": "^0.2.3",
70 | "karma": "^0.13.10",
71 | "karma-chrome-launcher": "^0.2.1",
72 | "karma-cli": "^0.1.1",
73 | "karma-mocha": "^0.2.0",
74 | "karma-sourcemap-loader": "^0.3.6",
75 | "karma-webpack": "^1.7.0",
76 | "less": "^2.2.0",
77 | "less-loader": "^2.0.0",
78 | "marked": "^0.3.2",
79 | "mocha": "^2.2.5",
80 | "null-loader": "^0.1.0",
81 | "postcss": "^4.0.2",
82 | "preact": "^8.2.4",
83 | "react": "^15.0.0-rc.2",
84 | "react-dnd-html5-backend": "^2.1.2",
85 | "react-dnd-test-backend": "^1.0.2",
86 | "react-dom": "^15.0.0-rc.2",
87 | "react-hot-loader": "^1.2.3",
88 | "react-router": "~0.13.2",
89 | "request": "2.46.0",
90 | "style-loader": "^0.8.3",
91 | "url-loader": "^0.5.5",
92 | "webpack": "^1.8.11",
93 | "webpack-dev-server": "^1.8.2"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # build minified standalone version in dist
4 | rm -rf dist
5 | ./node_modules/.bin/webpack --output-filename=dist/ReactDnD.js
6 | ./node_modules/.bin/webpack --output-filename=dist/ReactDnD.min.js --optimize-minimize
7 |
8 | # build ES5 modules to lib
9 | rm -rf lib
10 | ./node_modules/.bin/babel src --out-dir lib
11 |
--------------------------------------------------------------------------------
/scripts/buildSiteIndexPages.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('babel-core/register');
3 |
4 | // -*- mode: js -*-
5 | "use strict";
6 |
7 | var fs = require('fs');
8 | var path = require('path');
9 | var glob = require('glob');
10 | var Constants = require('../site/Constants');
11 | var renderPath = require('../__site_prerender__/renderPath');
12 | var flatten = require('lodash/flatten');
13 |
14 | var sitePath = path.join(__dirname, '../__site__');
15 | if (!fs.existsSync(sitePath)) {
16 | fs.mkdirSync(sitePath);
17 | }
18 |
19 | var files = {
20 | 'main.css': 'main.css',
21 | 'main.js': 'main.js'
22 | };
23 |
24 | if (process.env.NODE_ENV === 'production') {
25 | Object.keys(files).forEach(function(fileName) {
26 | var searchPath = path.join(
27 | __dirname,
28 | '../__site__/' + fileName.replace('.', '-*.')
29 | );
30 | var hashedFilename = glob.sync(searchPath)[0];
31 | if (!hashedFilename) {
32 | throw new Error(
33 | 'Hashed file of "' + fileName + '" ' +
34 | 'not found when searching with "' + searchPath + '"'
35 | );
36 | }
37 |
38 | files[fileName] = path.basename(hashedFilename);
39 | });
40 | }
41 |
42 | var locations = flatten([
43 | Constants.APIPages.map(function (group) {
44 | return group.pages;
45 | }),
46 | Constants.ExamplePages.map(function (group) {
47 | return group.pages;
48 | }),
49 | Constants.Pages
50 | ]).reduce(function(paths, pages) {
51 | return paths.concat(
52 | Object.keys(pages).map(function(key) {
53 | return pages[key].location;
54 | })
55 | );
56 | }, []);
57 |
58 | locations.forEach(function(fileName) {
59 | var props = {
60 | location: fileName,
61 | devMode: process.env.NODE_ENV !== 'production',
62 | files: files
63 | };
64 |
65 | renderPath(fileName, props, function(content) {
66 | fs.writeFileSync(
67 | path.join(sitePath, fileName),
68 | content
69 | );
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/scripts/buildStaticSite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | PATH=$(npm bin):$PATH
4 |
5 | rm -rf ./__site__
6 | rm -rf ./__site_prerender__
7 | NODE_ENV=production webpack --config "$PWD/site/webpack-client.config.js"
8 | NODE_ENV=production webpack --config "$PWD/site/webpack-prerender.config.js"
9 | NODE_ENV=production ./scripts/buildSiteIndexPages.sh
10 |
--------------------------------------------------------------------------------
/scripts/cssTransformLoader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var postcss = require('postcss');
4 | var autoPrefixer = require('autoprefixer');
5 |
6 | module.exports = function(content) {
7 | if (this && this.cacheable) {
8 | // Webpack specific call
9 | this.cacheable();
10 | }
11 |
12 | content = postcss()
13 | .use(autoPrefixer())
14 | .process(content).css;
15 |
16 | return content;
17 | };
18 |
--------------------------------------------------------------------------------
/scripts/markdownLoader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var marked = require('marked');
4 | var prism = require('./prism');
5 |
6 | // functions come before keywords
7 | prism.languages.insertBefore('javascript', 'keyword', {
8 | 'var': /\b(this)\b/g,
9 | 'block-keyword': /\b(if|else|while|for|function)\b/g,
10 | 'primitive': /\b(true|false|null|undefined)\b/g,
11 | 'function': prism.languages.function,
12 | });
13 |
14 | prism.languages.insertBefore('javascript', {
15 | 'qualifier': /\b[A-Z][a-z0-9_]+/g,
16 | });
17 |
18 | marked.setOptions({
19 | xhtml: true,
20 | highlight: function(code) {
21 | return prism.highlight(code, prism.languages.javascript);
22 | }
23 | });
24 |
25 | var renderer = new marked.Renderer();
26 |
27 | renderer.heading = function (text, level) {
28 | var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
29 |
30 | // A hack to have proper anchor scrolling despite the navbar on top of them.
31 | // In CSS, they'll be positioned relatively.
32 | return '' + text + ' ';
33 | };
34 |
35 | renderer.code = function(code, lang, escaped) {
36 | if (this.options.highlight) {
37 | var out = this.options.highlight(code, lang);
38 | if (out != null && out !== code) {
39 | escaped = true;
40 | code = out;
41 | }
42 | }
43 | return '' +
44 | (escaped ? code : escapeCode(code, true)) +
45 | '
';
46 | };
47 |
48 | function escapeCode(code) {
49 | return code
50 | .replace(/&/g, '&')
51 | .replace(//g, '>')
53 | .replace(/"/g, '"')
54 | .replace(/'/g, ''');
55 | }
56 |
57 | module.exports = function(markdown) {
58 | if (this && this.cacheable) {
59 | // Webpack specific call
60 | this.cacheable();
61 | }
62 |
63 | return marked(markdown, { renderer: renderer });
64 | };
65 |
--------------------------------------------------------------------------------
/scripts/publishStaticSite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | PROJECT_DIRECTORY="react-dnd"
5 | SITE_DIRECTORY="$PROJECT_DIRECTORY-site"
6 | GITHUB_REPO="git@github.com:gaearon/react-dnd.git"
7 | GH_PAGES_SITE="http://gaearon.github.io/react-dnd/"
8 |
9 | # Move to parent dir
10 | cd ../
11 |
12 | # Setup repo if doesnt exist
13 | if [ ! -d "$SITE_DIRECTORY" ]; then
14 | read -p "No site repo setup, can I create it at \"$PWD/$SITE_DIRECTORY\"? [Y/n] " -r
15 | echo
16 | if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ ! $REPLY == "" ]]
17 | then
18 | echo "Exit by user"
19 | exit 1
20 | fi
21 | git clone "$GITHUB_REPO" "$SITE_DIRECTORY"
22 | cd "$SITE_DIRECTORY"
23 | git checkout origin/gh-pages
24 | git checkout -b gh-pages
25 | git push --set-upstream origin gh-pages
26 | cd ../
27 | fi
28 |
29 | cd "$PROJECT_DIRECTORY"
30 | npm run build-site
31 | open __site__/index.html
32 | cd ../
33 |
34 | echo
35 | echo
36 | read -p "Are you ready to publish? [Y/n] " -r
37 | echo
38 | if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ ! $REPLY == "" ]]
39 | then
40 | echo "Exit by user"
41 | exit 1
42 | fi
43 |
44 | cd "$SITE_DIRECTORY"
45 | git reset --hard
46 | git checkout -- .
47 | git clean -dfx
48 | git fetch
49 | git rebase
50 | rm -Rf *
51 | echo "$PWD"
52 | cp -R ../$PROJECT_DIRECTORY/__site__/* .
53 | git add --all
54 | git commit -m "Update website"
55 | git push
56 | sleep 1
57 | open $GH_PAGES_SITE
58 |
--------------------------------------------------------------------------------
/scripts/resolvers.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var glob = require('glob');
3 | var path = require('path');
4 |
5 | function buildHasteMap() {
6 | var root = path.resolve(__dirname, '../src');
7 | var hasteMap = {};
8 | glob.sync(root + '/**/*.{js,css}').forEach(function(file) {
9 | var code = fs.readFileSync(file);
10 | var regex = /@providesModule ([^\s*]+)/;
11 | var result = regex.exec(code);
12 | if (result) {
13 | var id = result[1];
14 | if (path.extname(file) === '.css') {
15 | id += '.css';
16 | }
17 | hasteMap[id] = file;
18 | }
19 | });
20 | return hasteMap;
21 | };
22 |
23 | function resolveHasteDefines() {
24 | // Run in the context of webpack's compiler.
25 | var hasteMap = buildHasteMap();
26 | this.resolvers.normal.plugin('module', function(request, callback) {
27 | var hastePath = hasteMap[request.request];
28 | if (hastePath) {
29 | return callback(null, {
30 | path: hastePath,
31 | query: request.query,
32 | file: true,
33 | resolved: true
34 | });
35 | }
36 | return callback();
37 | });
38 | }
39 |
40 | module.exports = {
41 | resolveHasteDefines: resolveHasteDefines
42 | };
43 |
--------------------------------------------------------------------------------
/scripts/startSiteDevServer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | PATH=$(npm bin):$PATH
4 |
5 | rm -rf ./__site__
6 | rm -rf ./__site_prerender__
7 | ./node_modules/.bin/webpack --config "$PWD/site/webpack-prerender.config.js"
8 | ./scripts/buildSiteIndexPages.sh
9 | ./node_modules/.bin/webpack-dev-server --config "$PWD/site/webpack-client.config.js" --hot --content-base __site__
10 |
--------------------------------------------------------------------------------
/site/Constants.js:
--------------------------------------------------------------------------------
1 | export const Pages = {
2 | HOME: {
3 | location: 'index.html',
4 | title: 'Home'
5 | }
6 | };
7 |
8 | export const APIPages = [{
9 | title: 'Quick Start',
10 | pages: {
11 | OVERVIEW: {
12 | location: 'docs-overview.html',
13 | title: 'Overview'
14 | },
15 | TUTORIAL: {
16 | location: 'docs-tutorial.html',
17 | title: 'Tutorial'
18 | },
19 | TESTING: {
20 | location: 'docs-testing.html',
21 | title: 'Testing'
22 | },
23 | FAQ: {
24 | location: 'docs-faq.html',
25 | title: 'FAQ'
26 | },
27 | TROUBLESHOOTING: {
28 | location: 'docs-troubleshooting.html',
29 | title: 'Troubleshooting'
30 | }
31 | }
32 | }, {
33 | title: 'Top-Level API',
34 | pages: {
35 | DRAG_SOURCE: {
36 | location: 'docs-drag-source.html',
37 | title: 'DragSource'
38 | },
39 | DROP_TARGET: {
40 | location: 'docs-drop-target.html',
41 | title: 'DropTarget'
42 | },
43 | DRAG_LAYER: {
44 | location: 'docs-drag-layer.html',
45 | title: 'DragLayer'
46 | },
47 | DRAG_DROP_CONTEXT: {
48 | location: 'docs-drag-drop-context.html',
49 | title: 'DragDropContext'
50 | }
51 | }
52 | }, {
53 | title: 'Connecting to DOM',
54 | pages: {
55 | DRAG_SOURCE_CONNECTOR: {
56 | location: 'docs-drag-source-connector.html',
57 | title: 'DragSourceConnector'
58 | },
59 | DROP_TARGET_CONNECTOR: {
60 | location: 'docs-drop-target-connector.html',
61 | title: 'DropTargetConnector'
62 | }
63 | }
64 | }, {
65 | title: 'Monitoring State',
66 | pages: {
67 | DRAG_SOURCE_MONITOR: {
68 | location: 'docs-drag-source-monitor.html',
69 | title: 'DragSourceMonitor'
70 | },
71 | DROP_TARGET_MONITOR: {
72 | location: 'docs-drop-target-monitor.html',
73 | title: 'DropTargetMonitor'
74 | },
75 | DRAG_LAYER_MONITOR: {
76 | location: 'docs-drag-layer-monitor.html',
77 | title: 'DragLayerMonitor'
78 | }
79 | }
80 | }, {
81 | title: 'Backends',
82 | pages: {
83 | HTML5_BACKEND: {
84 | location: 'docs-html5-backend.html',
85 | title: 'HTML5'
86 | },
87 | TEST_BACKEND: {
88 | location: 'docs-test-backend.html',
89 | title: 'Test'
90 | }
91 | }
92 | }];
93 |
94 | export const ExamplePages = [{
95 | title: 'Chessboard',
96 | pages: {
97 | CHESSBOARD_TUTORIAL_APP: {
98 | location: 'examples-chessboard-tutorial-app.html',
99 | title: 'Tutorial App'
100 | }
101 | }
102 | }, {
103 | title: 'Dustbin',
104 | pages: {
105 | DUSTBIN_SINGLE_TARGET: {
106 | location: 'examples-dustbin-single-target.html',
107 | title: 'Single Target'
108 | },
109 | DUSTBIN_MULTIPLE_TARGETS: {
110 | location: 'examples-dustbin-multiple-targets.html',
111 | title: 'Multiple Targets'
112 | },
113 | DUSTBIN_STRESS_TEST: {
114 | location: 'examples-dustbin-stress-test.html',
115 | title: 'Stress Test'
116 | }
117 | }
118 | }, {
119 | title: 'Drag Around',
120 | pages: {
121 | DRAG_AROUND_NAIVE: {
122 | location: 'examples-drag-around-naive.html',
123 | title: 'Naive'
124 | },
125 | DRAG_AROUND_CUSTOM_DRAG_LAYER: {
126 | location: 'examples-drag-around-custom-drag-layer.html',
127 | title: 'Custom Drag Layer'
128 | }
129 | }
130 | }, {
131 | title: 'Nesting',
132 | pages: {
133 | NESTING_DRAG_SOURCES: {
134 | location: 'examples-nesting-drag-sources.html',
135 | title: 'Drag Sources'
136 | },
137 | NESTING_DROP_TARGETS: {
138 | location: 'examples-nesting-drop-targets.html',
139 | title: 'Drop Targets'
140 | }
141 | }
142 | }, {
143 | title: 'Sortable',
144 | pages: {
145 | SORTABLE_SIMPLE: {
146 | location: 'examples-sortable-simple.html',
147 | title: 'Simple'
148 | },
149 | SORTABLE_CANCEL_ON_DROP_OUTSIDE: {
150 | location: 'examples-sortable-cancel-on-drop-outside.html',
151 | title: 'Cancel on Drop Outside'
152 | },
153 | SORTABLE_STRESS_TEST: {
154 | location: 'examples-sortable-stress.html',
155 | title: 'Stress Test'
156 | }
157 | }
158 | }, {
159 | title: 'Customize',
160 | pages: {
161 | CUSTOMIZE_HANDLES_AND_PREVIEWS: {
162 | location: 'examples-customize-handles-and-previews.html',
163 | title: 'Handles and Previews'
164 | },
165 | CUSTOMIZE_DROP_EFFECTS: {
166 | location: 'examples-customize-drop-effects.html',
167 | title: 'Drop Effects'
168 | }
169 | }
170 | }];
171 |
172 | export const DOCS_DEFAULT = APIPages[0].pages.OVERVIEW;
173 | export const EXAMPLES_DEFAULT = ExamplePages[0].pages.CHESSBOARD_TUTORIAL_APP;
--------------------------------------------------------------------------------
/site/IndexPage.js:
--------------------------------------------------------------------------------
1 | import './base.less';
2 | import Constants, { APIPages, ExamplePages, Pages } from './Constants';
3 | import HomePage from './pages/HomePage';
4 | import APIPage from './pages/APIPage';
5 | import ExamplePage from './pages/ExamplePage';
6 | import React, { Component } from 'react';
7 | import ReactDOMServer from 'react-dom/server';
8 |
9 | const APIDocs = {
10 | OVERVIEW: require('../docs/00 Quick Start/Overview.md'),
11 | TUTORIAL: require('../docs/00 Quick Start/Tutorial.md'),
12 | TESTING: require('../docs/00 Quick Start/Testing.md'),
13 | FAQ: require('../docs/00 Quick Start/FAQ.md'),
14 | TROUBLESHOOTING: require('../docs/00 Quick Start/Troubleshooting.md'),
15 | DRAG_SOURCE: require('../docs/01 Top Level API/DragSource.md'),
16 | DRAG_SOURCE_MONITOR: require('../docs/03 Monitoring State/DragSourceMonitor.md'),
17 | DRAG_SOURCE_CONNECTOR: require('../docs/02 Connecting to DOM/DragSourceConnector.md'),
18 | DROP_TARGET: require('../docs/01 Top Level API/DropTarget.md'),
19 | DROP_TARGET_CONNECTOR: require('../docs/02 Connecting to DOM/DropTargetConnector.md'),
20 | DROP_TARGET_MONITOR: require('../docs/03 Monitoring State/DropTargetMonitor.md'),
21 | DRAG_DROP_CONTEXT: require('../docs/01 Top Level API/DragDropContext.md'),
22 | DRAG_LAYER: require('../docs/01 Top Level API/DragLayer.md'),
23 | DRAG_LAYER_MONITOR: require('../docs/03 Monitoring State/DragLayerMonitor.md'),
24 | HTML5_BACKEND: require('../docs/04 Backends/HTML5.md'),
25 | TEST_BACKEND: require('../docs/04 Backends/Test.md')
26 | };
27 |
28 | const Examples = {
29 | CHESSBOARD_TUTORIAL_APP: require('../examples/00 Chessboard/Tutorial App'),
30 | DUSTBIN_SINGLE_TARGET: require('../examples/01 Dustbin/Single Target'),
31 | DUSTBIN_MULTIPLE_TARGETS: require('../examples/01 Dustbin/Multiple Targets'),
32 | DUSTBIN_STRESS_TEST: require('../examples/01 Dustbin/Stress Test'),
33 | DRAG_AROUND_NAIVE: require('../examples/02 Drag Around/Naive'),
34 | DRAG_AROUND_CUSTOM_DRAG_LAYER: require('../examples/02 Drag Around/Custom Drag Layer'),
35 | NESTING_DRAG_SOURCES: require('../examples/03 Nesting/Drag Sources'),
36 | NESTING_DROP_TARGETS: require('../examples/03 Nesting/Drop Targets'),
37 | SORTABLE_SIMPLE: require('../examples/04 Sortable/Simple'),
38 | SORTABLE_CANCEL_ON_DROP_OUTSIDE: require('../examples/04 Sortable/Cancel on Drop Outside'),
39 | SORTABLE_STRESS_TEST: require('../examples/04 Sortable/Stress Test'),
40 | CUSTOMIZE_HANDLES_AND_PREVIEWS: require('../examples/05 Customize/Handles and Previews'),
41 | CUSTOMIZE_DROP_EFFECTS: require('../examples/05 Customize/Drop Effects')
42 | };
43 |
44 | export default class IndexPage extends Component {
45 | static getDoctype() {
46 | return '';
47 | }
48 |
49 | static renderToString(props) {
50 | return IndexPage.getDoctype() +
51 | ReactDOMServer.renderToString( );
52 | }
53 |
54 | constructor(props) {
55 | super(props);
56 | this.state = {
57 | renderPage: !this.props.devMode
58 | };
59 | }
60 |
61 | render() {
62 | // Dump out our current props to a global object via a script tag so
63 | // when initialising the browser environment we can bootstrap from the
64 | // same props as what each page was rendered with.
65 | const browserInitScriptObj = {
66 | __html: 'window.INITIAL_PROPS = ' + JSON.stringify(this.props) + ';\n'
67 | };
68 |
69 | return (
70 |
71 |
72 |
73 | React DnD
74 |
75 |
76 |
77 |
78 |
79 | {this.state.renderPage && this.renderPage()}
80 |
81 |
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | renderPage() {
89 | switch (this.props.location) {
90 | case Pages.HOME.location:
91 | return ;
92 | }
93 |
94 | for (let groupIndex in APIPages) {
95 | const group = APIPages[groupIndex];
96 | const pageKeys = Object.keys(group.pages);
97 |
98 | for (let i = 0; i < pageKeys.length; i++) {
99 | const key = pageKeys[i];
100 | const page = group.pages[key];
101 |
102 | if (this.props.location === page.location) {
103 | return ;
105 | }
106 | }
107 | }
108 |
109 | for (let groupIndex in ExamplePages) {
110 | const group = ExamplePages[groupIndex];
111 | const pageKeys = Object.keys(group.pages);
112 |
113 | for (let i = 0; i < pageKeys.length; i++) {
114 | const key = pageKeys[i];
115 | const page = group.pages[key];
116 | const Component = Examples[key];
117 |
118 | if (this.props.location === page.location) {
119 | return (
120 |
121 |
122 |
123 | );
124 | }
125 | }
126 | }
127 |
128 | throw new Error(
129 | 'Page of location ' +
130 | JSON.stringify(this.props.location) +
131 | ' not found.'
132 | );
133 | }
134 |
135 | componentDidMount() {
136 | if (!this.state.renderPage) {
137 | this.setState({
138 | renderPage: true
139 | });
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/site/LICENSE:
--------------------------------------------------------------------------------
1 | BSD License
2 |
3 | Copyright (c) 2015, Facebook, Inc. All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | * Neither the name Facebook nor the names of its contributors may be used to
16 | endorse or promote products derived from this software without specific
17 | prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/site/README.md:
--------------------------------------------------------------------------------
1 | React DnD website is based on the [fixed-data-table](https://github.com/facebook/fixed-data-table) website and uses its code with a permission.
2 |
3 | View it on the web: http://gaearon.github.io/react-dnd
4 |
5 | Run it locally:
6 |
7 | ```
8 | npm install
9 | npm start
10 | ```
11 |
12 | After it builds the static site (might take about half a minute), open http://localhost:8080/.
13 |
--------------------------------------------------------------------------------
/site/base.less:
--------------------------------------------------------------------------------
1 | @import './constants.less';
2 |
3 | * { box-sizing: border-box; }
4 |
5 | html, body {
6 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
7 | -webkit-text-size-adjust: 100%;
8 | -ms-text-size-adjust: 100%;
9 | margin: 0;
10 | padding: 0;
11 | -webkit-font-smoothing: antialiased;
12 | text-rendering: optimizeLegibility;
13 | color: @body-color;
14 | font-family: 'Helvetica Neue', Helvetica, sans-serif;
15 | font-size: 14px;
16 | line-height: 1.625;
17 | }
18 |
19 | @media only screen and (min-width: @screen-tablet) {
20 | body {
21 | font-size: 18px;
22 | }
23 | }
24 |
25 | h1, h2, h3, h4, h5, h6 {
26 | color: @header-color;
27 | }
28 |
29 | a[href] {
30 | color: @link-color;
31 | text-decoration: none;
32 | }
33 |
34 | a[id]:not([href]) {
35 | // This is quite a badass hack.
36 | // We really those anchors despite the navbar!
37 | position: relative;
38 | top: -@navbar-height;
39 | }
40 |
41 | pre, code {
42 | font-family: Consolas, 'Source Code Pro', Menlo, monospace;
43 | background: #F9F8F7;
44 | color: #484A4C;
45 | // font-size: 1em;
46 | // letter-spacing: -0.015em;
47 | }
48 |
49 | a code {
50 | color: inherit;
51 | }
52 |
53 | code {
54 | margin: -0.05rem -0.15em;
55 | padding: 0.05rem 0.35em;
56 | }
57 |
58 | blockquote {
59 | margin: 1rem 0;
60 | padding: 0 1rem;
61 | color: #727476;
62 | border-left: solid 3px #DCDAD9;
63 | }
64 |
65 | blockquote > :first-child {
66 | margin-top: 0;
67 | }
68 |
69 | blockquote > :last-child {
70 | margin-bottom: 0;
71 | }
72 |
73 | hr {
74 | border: 1px solid;
75 | color: @body-color;
76 | opacity: 0.1;
77 | }
78 |
79 |
80 | // Markdown
81 |
82 | .codeBlock {
83 | -webkit-overflow-scrolling: touch;
84 | background: #FCFBFA;
85 | border-left: solid 3px #ECEAE9;
86 | box-sizing: border-box;
87 | display: block;
88 | // font-size: 0.875em;
89 | margin: 0.5rem 0;
90 | overflow-y: scroll;
91 | padding: 0.5rem 8px 0.5rem 12px;
92 | white-space: pre;
93 | }
94 |
95 | .t.blockParams {
96 | padding-left: 2ch;
97 | }
98 |
99 | // TODO: not random colors
100 |
101 | .token.punctuation,
102 | .token.ignore,
103 | .t.interfaceDef,
104 | .t.member,
105 | .t.callSig {
106 | color: #808890;
107 | }
108 |
109 | .token.function,
110 | .token.class-name,
111 | .token.qualifier,
112 | .t.fnQualifier,
113 | .t.fnName {
114 | color: #32308E;
115 | }
116 |
117 | .token.primitive,
118 | .t.primitive {
119 | color: #922;
120 | }
121 |
122 | .token.number,
123 | .t.typeParam {
124 | color: #905;
125 | }
126 |
127 | .t.typeQualifier,
128 | .t.typeName {
129 | color: #013679;
130 | }
131 |
132 | .t.param {
133 | color: #945277;
134 | }
135 |
136 | .t.memberName {
137 | color: teal;
138 | }
139 |
140 | .token.block-keyword,
141 | .token.keyword,
142 | .t.keyword {
143 | color: #A51;
144 | }
145 |
146 | .token.string,
147 | .token.regex {
148 | color: #df5050;
149 | }
150 |
151 | .token.operator {
152 | color: #a67f59;
153 | }
154 |
155 | .token.comment {
156 | color: #998;
157 | font-style: italic;
158 | }
159 |
--------------------------------------------------------------------------------
/site/client.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import IndexPage from './IndexPage';
4 |
5 | render(
6 | ,
9 | document
10 | );
11 |
--------------------------------------------------------------------------------
/site/components/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import { findDOMNode } from 'react-dom';
3 | import ReactUpdates from 'react/lib/ReactUpdates';
4 | import StaticHTMLBlock from './StaticHTMLBlock';
5 |
6 | import './CodeBlock.less';
7 |
8 | let preferredSyntax = 'es5';
9 | let observers = [];
10 |
11 | function subscribe(observer) {
12 | observers.push(observer);
13 | return () => {
14 | observers.slice(observers.indexOf(observer), 1);
15 | };
16 | }
17 |
18 | function setPreferredSyntax(syntax) {
19 | preferredSyntax = syntax;
20 | observers.forEach(o => o(preferredSyntax));
21 | }
22 |
23 | export default class CodeBlock extends Component {
24 | static propTypes = {
25 | es5: PropTypes.string,
26 | es6: PropTypes.string,
27 | es7: PropTypes.string
28 | };
29 |
30 | static defaultProps = {
31 | es5: '',
32 | es6: '',
33 | es7: ''
34 | };
35 |
36 | constructor(props) {
37 | super(props);
38 | this.state = {
39 | chosen: false,
40 | syntax: this.props.es5.trim().length && 'es5' ||
41 | this.props.es6.trim().length && 'es6' ||
42 | this.props.es7.trim().length && 'es7'
43 | };
44 | }
45 |
46 | componentDidMount() {
47 | this.unsubscribe = subscribe(this.handlePreferredSyntaxChange.bind(this));
48 | }
49 |
50 | handlePreferredSyntaxChange(syntax) {
51 | if (this.state.chosen || this.state.syntax === syntax) {
52 | return;
53 | }
54 |
55 | if (this.props[syntax].trim().length) {
56 | this.setState({
57 | syntax
58 | });
59 | }
60 | }
61 |
62 | componentWillUnmount() {
63 | this.unsubscribe();
64 | }
65 |
66 | handleSyntaxClick(syntax) {
67 | this.setState({
68 | syntax,
69 | chosen: true
70 | });
71 |
72 | const scrollTopBefore = findDOMNode(this).getBoundingClientRect().top;
73 | setPreferredSyntax(syntax);
74 | ReactUpdates.flushBatchedUpdates();
75 | const scrollTopAfter = findDOMNode(this).getBoundingClientRect().top;
76 |
77 | window.scroll(
78 | window.pageXOffset || window.scrollX,
79 | (window.pageYOffset || window.scrollY) - (scrollTopBefore - scrollTopAfter)
80 | );
81 | }
82 |
83 | render() {
84 | return (
85 |
86 |
87 | {['es5', 'es6', 'es7'].map(this.renderSyntaxLink, this)}
88 |
89 |
90 |
91 |
92 |
93 | );
94 | }
95 |
96 | renderSyntaxLink(syntax) {
97 | if (!this.props[syntax] || !this.props[syntax].trim().length) {
98 | return;
99 | }
100 |
101 | if (syntax === 'es5' &&
102 | !this.props.es6.trim().length &&
103 | !this.props.es7.trim().length) {
104 | return;
105 | }
106 |
107 | const active = this.state.syntax === syntax;
108 | return (
109 |
111 |
112 | {syntax.toUpperCase()}
113 |
114 |
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/site/components/CodeBlock.less:
--------------------------------------------------------------------------------
1 | @import '../constants.less';
2 |
3 | .CodeBlock-tabs {
4 | padding: 0;
5 | }
6 |
7 | .CodeBlock-tab {
8 | display: inline-block;
9 | margin-right: 10px;
10 | }
11 |
12 | .CodeBlock-tab a {
13 | cursor: pointer;
14 | color: @body-color;
15 | }
16 |
17 | .CodeBlock-activeTab a {
18 | color: @accent-color;
19 | }
--------------------------------------------------------------------------------
/site/components/Cover.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Cover.less';
3 |
4 | export default class Cover extends Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 | Drag and Drop for React
11 |
12 |
13 |
14 | );
15 | }
16 | }
--------------------------------------------------------------------------------
/site/components/Cover.less:
--------------------------------------------------------------------------------
1 | @import '../constants.less';
2 |
3 | .Cover {
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | // background-color: @color-darkBlue;
9 | padding: 2em;
10 | overflow: hidden;
11 | top: 0;
12 | width: 100%;
13 | z-index: 1;
14 | border-bottom: inset 1px solid rgba(255,255,255,0.1);
15 |
16 | .Cover-header {
17 | margin: 6rem 0 3rem;
18 | text-align: center;
19 | }
20 |
21 | .Cover-logo {
22 | margin: 0;
23 | font-size: 7vw;
24 | font-weight: normal;
25 | line-height: 1;
26 | letter-spacing: -0.025em;
27 | // color: @color-lightBlue;
28 | }
29 |
30 | .Cover-description {
31 | margin: 0;
32 | font-size: 2.25em;
33 | line-height: 1.3;
34 | // color: @color-lightBlue;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/site/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import NavBar from './NavBar';
3 | import Cover from './Cover';
4 | import './Header.less';
5 |
6 | export default class Header extends Component {
7 | render() {
8 | return (
9 |
12 | );
13 | }
14 | }
--------------------------------------------------------------------------------
/site/components/Header.less:
--------------------------------------------------------------------------------
1 | .Header {
2 | overflow: hidden; //clearfix
3 | }
4 |
--------------------------------------------------------------------------------
/site/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { DOCS_DEFAULT, EXAMPLES_DEFAULT } from '../Constants';
3 | import './NavBar.less';
4 |
5 | const GITHUB_URL = 'https://github.com/gaearon/react-dnd';
6 | const DOCS_LOCATION = DOCS_DEFAULT.location;
7 | const EXAMPLES_LOCATION = EXAMPLES_DEFAULT.location;
8 |
9 | export default class NavBar extends Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
React DnD
16 |
Drag and Drop for React
17 |
18 |
19 |
24 |
25 |
26 | );
27 | }
28 | }
--------------------------------------------------------------------------------
/site/components/NavBar.less:
--------------------------------------------------------------------------------
1 | @import '../constants.less';
2 |
3 | .NavBar {
4 | z-index: 3;
5 | top: 0;
6 | position: fixed;
7 | width: 100%;
8 | padding: 0 @content-padding;
9 | background-color: fade(#fff, 90%);
10 |
11 | .NavBar-container {
12 | display: flex;
13 | justify-content: space-between;
14 | align-items: center;
15 | margin: 0 auto;
16 | height: @navbar-height;
17 | padding: @content-padding/2 0;
18 | border-bottom: 2px solid fade(@accent-color, 10%);
19 | }
20 |
21 | .NavBar-logo {
22 | color: @accent-color;
23 | line-height: 1.4;
24 | }
25 |
26 | .NavBar-logoTitle {
27 | font-weight: bold;
28 | font-size: 1.125em;
29 | }
30 |
31 | .NavBar-logoDescription {
32 | margin: 0;
33 | font-size: .875em;
34 | }
35 |
36 | .NavBar-link {
37 | color: @accent-color;
38 | }
39 |
40 | .NavBar-link + .NavBar-link {
41 | margin-left: 1em;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/site/components/PageBody.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './PageBody.less';
3 |
4 | export default class PageBody extends Component {
5 | static propTypes = {
6 | hasSidebar: React.PropTypes.bool
7 | };
8 |
9 | render() {
10 | var {hasSidebar, html, ...props} = this.props;
11 | return (
12 |
13 |
14 | {this.props.children}
15 |
16 |
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/site/components/PageBody.less:
--------------------------------------------------------------------------------
1 | @import '../constants.less';
2 |
3 | .PageBody {
4 | padding: @content-padding*4 @content-padding;
5 | background-color: #fff;
6 |
7 | .PageBody-container {
8 | display: flex;
9 | flex-direction: column;
10 | margin: 0 auto;
11 | max-width: @content-width;
12 |
13 | @media only screen and (min-width: @screen-tablet) {
14 | flex-direction: row;
15 | }
16 | }
17 | }
18 |
19 | .PageBody.PageBody--hasSidebar {
20 | .PageBody-container {
21 | @media only screen and (min-width: @screen-tablet) {
22 | margin-left: @sidebar-width + @content-padding*2;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/site/components/SideBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './SideBar.less';
3 |
4 | export default class SideBar extends Component {
5 | render() {
6 | return (
7 |
8 |
9 | {this.props.groups.map(this.renderGroup, this)}
10 |
11 |
12 | );
13 | }
14 |
15 | renderGroup({ title, pages}, index) {
16 | return (
17 |
18 |
19 | {title}
20 |
21 | {Object.keys(pages).map(key => this.renderLink(pages[key], key))}
22 |
23 | );
24 | }
25 |
26 | renderLink({ title, location }, key) {
27 | const arrow = ;
28 |
29 | let linkClass = 'SideBar-item';
30 | if (this.props.example.location === location) {
31 | linkClass += ' SideBar-item--selected';
32 | }
33 |
34 | return (
35 |
36 | {title}
37 | {arrow}
38 |
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/site/components/SideBar.less:
--------------------------------------------------------------------------------
1 | @import '../constants.less';
2 |
3 | .SideBar {
4 | flex-shrink: 0;
5 |
6 | @media only screen and (min-width: @screen-tablet) {
7 | position: fixed;
8 | left: @content-padding;
9 | top: @navbar-height;
10 | bottom: 0;
11 | width: @sidebar-width;
12 | overflow: scroll;
13 | padding: @content-padding*2 0;
14 | }
15 |
16 | .SideBar-group + .SideBar-group {
17 | margin-top: @content-padding;
18 | }
19 |
20 | .SideBar-groupTitle {
21 | border-bottom: 2px solid fade(@accent-color, 10%);
22 | padding-bottom: .5em;
23 | margin: 0 0 .5em 0;
24 | }
25 |
26 | .SideBar-item {
27 | display: block;
28 | color: @body-color;
29 |
30 | &.SideBar-item--selected {
31 | color: @accent-color;
32 | position: relative;
33 |
34 | &:after {
35 | content: '·';
36 | position: absolute;
37 | left: -.5em; top: 0; bottom: 0;
38 | // color: fade(@accent-color, 10%);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/site/components/StaticHTMLBlock.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import CodeBlock from './CodeBlock';
3 |
4 | export default class StaticHTMLBlock extends Component {
5 | static propTypes = {
6 | html: React.PropTypes.string.isRequired
7 | };
8 |
9 | render() {
10 | const { html } = this.props;
11 |
12 | // Here goes a really hack-ish way to convert
13 | // areas separated by Markdown s into code tabs.
14 |
15 | const blocks = html.split(' ');
16 | const elements = [];
17 |
18 | let es5Content = null;
19 | let es6Content = null;
20 | let es7Content = null;
21 |
22 | for (let i = 0; i < blocks.length; i++) {
23 | const content = blocks[i];
24 |
25 | switch (i % 4) {
26 | case 0:
27 | elements.push(
28 |
31 | );
32 | break;
33 | case 1:
34 | es5Content = content;
35 | break;
36 | case 2:
37 | es6Content = content;
38 | break;
39 | case 3:
40 | es7Content = content;
41 | elements.push(
42 |
46 | );
47 | break;
48 | }
49 | }
50 |
51 | return (
52 |
53 | {elements}
54 |
55 | );
56 | }
57 | }
--------------------------------------------------------------------------------
/site/constants.less:
--------------------------------------------------------------------------------
1 | @accent-color: #0074D9;
2 |
3 | @link-color: @accent-color;
4 | @header-color: #212325;
5 | @body-color: #626466;
6 |
7 | @navbar-height: 5em;
8 |
9 | @content-width: 52em;
10 | @content-padding: 1.5em;
11 |
12 | @sidebar-width: 12em;
13 |
14 | @screen-tablet: 680px;
15 |
--------------------------------------------------------------------------------
/site/pages/APIPage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Header from '../components/Header';
3 | import PageBody from '../components/PageBody';
4 | import SideBar from '../components/SideBar';
5 | import StaticHTMLBlock from '../components/StaticHTMLBlock';
6 | import { APIPages } from '../Constants';
7 |
8 | export default class APIPage extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 | }
--------------------------------------------------------------------------------
/site/pages/ExamplePage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Header from '../components/Header';
3 | import PageBody from '../components/PageBody';
4 | import SideBar from '../components/SideBar';
5 | import { ExamplePages } from '../Constants';
6 |
7 | export default class ExamplesPage extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
17 |
18 | {this.props.children}
19 |
20 |
21 | );
22 | }
23 | }
--------------------------------------------------------------------------------
/site/pages/HomePage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Header from '../components/Header';
3 | import PageBody from '../components/PageBody';
4 | import StaticHTMLBlock from '../components/StaticHTMLBlock';
5 | import IndexHTML from '../../docs/index.md';
6 |
7 | export default class HomePage extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/site/renderPath.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IndexPage from './IndexPage';
3 |
4 | export default function renderPath(path, props, onRender) {
5 | onRender(
6 | IndexPage.renderToString(props)
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/site/webpack-client.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var resolvers = require('../scripts/resolvers');
4 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
5 |
6 | var isDev = process.env.NODE_ENV !== 'production';
7 |
8 | module.exports = {
9 |
10 | devtool: isDev ? 'cheap-eval-source-map' : 'source-map',
11 |
12 | entry: [
13 | path.join(__dirname, 'client.js')
14 | ].concat(isDev ? [
15 | 'webpack-dev-server/client?http://localhost:8080',
16 | 'webpack/hot/only-dev-server'
17 | ] : []),
18 |
19 | output: {
20 | path: '__site__/',
21 | filename: isDev ? '[name].js' : '[name]-[hash].js',
22 | publicPath: ''
23 | },
24 |
25 | target: 'web',
26 |
27 | module: {
28 | loaders: [
29 | {
30 | test: /\.md$/,
31 | loader: [
32 | 'html?{"minimize":false}',
33 | path.join(__dirname, '../scripts/markdownLoader')
34 | ].join('!')
35 | },
36 | {
37 | test: /\.js$/,
38 | exclude: /node_modules/,
39 | loaders: isDev ? ['react-hot-loader', 'babel-loader'] : ['babel-loader']
40 | },
41 | {
42 | test: /\.less$/,
43 | loader: ExtractTextPlugin.extract(
44 | 'style-loader',
45 | [
46 | 'css-loader',
47 | path.join(__dirname, '../scripts/cssTransformLoader'),
48 | 'less-loader'
49 | ].join('!')
50 | )
51 | },
52 | {
53 | test: /\.png$/,
54 | loader: 'file-loader',
55 | query: { mimetype: 'image/png', name: 'images/[name]-[hash].[ext]' }
56 | }
57 | ]
58 | },
59 |
60 | resolve: {
61 | alias: {
62 | 'react-dnd/modules': path.join(__dirname, '../src'),
63 | 'react-dnd': path.join(__dirname, '../src')
64 | }
65 | },
66 |
67 | plugins: [
68 | new ExtractTextPlugin(
69 | isDev ? '[name].css' : '[name]-[hash].css'
70 | ),
71 | new webpack.optimize.OccurenceOrderPlugin(),
72 | new webpack.DefinePlugin({
73 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
74 | '__DEV__': JSON.stringify(isDev || true)
75 | }),
76 | resolvers.resolveHasteDefines,
77 | ]
78 | };
79 |
80 | if (process.env.NODE_ENV === 'production') {
81 | module.exports.plugins.push(
82 | new webpack.optimize.UglifyJsPlugin({
83 | compressor: {
84 | warnings: false
85 | }
86 | })
87 | );
88 | }
89 |
--------------------------------------------------------------------------------
/site/webpack-prerender.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var resolvers = require('../scripts/resolvers');
4 |
5 | var isDev = process.env.NODE_ENV !== 'production';
6 |
7 | module.exports = {
8 | entry: path.join(__dirname, 'renderPath.js'),
9 |
10 | output: {
11 | path: '__site_prerender__/',
12 | filename: 'renderPath.js',
13 | libraryTarget: 'commonjs2',
14 | },
15 |
16 | target: 'node',
17 |
18 | module: {
19 | loaders: [
20 | {
21 | test: /\.md$/,
22 | loader: [
23 | 'html?{"minimize":false}',
24 | path.join(__dirname, '../scripts/markdownLoader')
25 | ].join('!')
26 | },
27 | {
28 | test: /\.js$/,
29 | loader: 'babel-loader',
30 | exclude: /node_modules/
31 | },
32 | {
33 | test: /\.css$/,
34 | loader: 'null-loader'
35 | },
36 | {
37 | test: /\.less$/,
38 | loader: 'null-loader'
39 | },
40 | {
41 | test: /\.png$/,
42 | loader: 'file-loader',
43 | query: { mimetype: 'image/png', name: 'images/[name]-[hash].[ext]' }
44 | }
45 | ]
46 | },
47 |
48 | resolve: {
49 | alias: {
50 | 'react-dnd/modules': path.join(__dirname, '../src'),
51 | 'react-dnd': path.join(__dirname, '../src')
52 | }
53 | },
54 |
55 | plugins: [
56 | new webpack.optimize.OccurenceOrderPlugin(),
57 | new webpack.DefinePlugin({
58 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
59 | '__DEV__': JSON.stringify(isDev || true)
60 | }),
61 | resolvers.resolveHasteDefines,
62 | ]
63 | };
64 |
65 | if (process.env.NODE_ENV === 'production') {
66 | module.exports.plugins.push(
67 | new webpack.optimize.UglifyJsPlugin({
68 | compressor: {
69 | warnings: false
70 | }
71 | })
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/DragDropContext.js:
--------------------------------------------------------------------------------
1 | import preact, { Component } from 'preact';
2 | import { DragDropManager } from 'dnd-core';
3 | import invariant from 'invariant';
4 | import checkDecoratorArguments from './utils/checkDecoratorArguments';
5 | import hoistStatics from 'hoist-non-react-statics';
6 |
7 | export default function DragDropContext(backendOrModule) {
8 | checkDecoratorArguments('DragDropContext', 'backend', ...arguments);
9 |
10 | // Auto-detect ES6 default export for people still using ES5
11 | let backend;
12 | if (typeof backendOrModule === 'object' && typeof backendOrModule.default === 'function') {
13 | backend = backendOrModule.default;
14 | } else {
15 | backend = backendOrModule;
16 | }
17 |
18 | invariant(
19 | typeof backend === 'function',
20 | 'Expected the backend to be a function or an ES6 module exporting a default function. ' +
21 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-drop-context.html'
22 | );
23 |
24 | const childContext = {
25 | dragDropManager: new DragDropManager(backend)
26 | };
27 |
28 | return function decorateContext(DecoratedComponent) {
29 | const displayName =
30 | DecoratedComponent.displayName ||
31 | DecoratedComponent.name ||
32 | 'Component';
33 |
34 | class DragDropContextContainer extends Component {
35 | static DecoratedComponent = DecoratedComponent;
36 |
37 | static displayName = `DragDropContext(${displayName})`;
38 |
39 | getDecoratedComponentInstance() {
40 | return this.child;
41 | }
42 |
43 | getManager() {
44 | return childContext.dragDropManager;
45 | }
46 |
47 | getChildContext() {
48 | return childContext;
49 | }
50 |
51 | setChildRef = (child) => {
52 | this.child = child;
53 | }
54 |
55 | render() {
56 | return (
57 |
58 | );
59 | }
60 | }
61 |
62 | return hoistStatics(DragDropContextContainer, DecoratedComponent);
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/src/DragLayer.js:
--------------------------------------------------------------------------------
1 | import preact, { Component } from 'preact';
2 | import shallowEqual from './utils/shallowEqual';
3 | import shallowEqualScalar from './utils/shallowEqualScalar';
4 | import isPlainObject from 'lodash/isPlainObject';
5 | import invariant from 'invariant';
6 | import checkDecoratorArguments from './utils/checkDecoratorArguments';
7 | import hoistStatics from 'hoist-non-react-statics';
8 |
9 | export default function DragLayer(collect, options = {}) {
10 | checkDecoratorArguments('DragLayer', 'collect[, options]', ...arguments);
11 | invariant(
12 | typeof collect === 'function',
13 | 'Expected "collect" provided as the first argument to DragLayer ' +
14 | 'to be a function that collects props to inject into the component. ',
15 | 'Instead, received %s. ' +
16 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
17 | collect
18 | );
19 | invariant(
20 | isPlainObject(options),
21 | 'Expected "options" provided as the second argument to DragLayer to be ' +
22 | 'a plain object when specified. ' +
23 | 'Instead, received %s. ' +
24 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-layer.html',
25 | options
26 | );
27 |
28 | return function decorateLayer(DecoratedComponent) {
29 | const { arePropsEqual = shallowEqualScalar } = options;
30 | const displayName =
31 | DecoratedComponent.displayName ||
32 | DecoratedComponent.name ||
33 | 'Component';
34 |
35 | class DragLayerContainer extends Component {
36 | static DecoratedComponent = DecoratedComponent;
37 |
38 | static displayName = `DragLayer(${displayName})`;
39 |
40 | getDecoratedComponentInstance() {
41 | return this.child;
42 | }
43 |
44 | shouldComponentUpdate(nextProps, nextState) {
45 | return !arePropsEqual(nextProps, this.props) ||
46 | !shallowEqual(nextState, this.state);
47 | }
48 |
49 | constructor(props, context) {
50 | super(props);
51 | this.handleChange = this.handleChange.bind(this);
52 |
53 | this.manager = context.dragDropManager;
54 | invariant(
55 | typeof this.manager === 'object',
56 | 'Could not find the drag and drop manager in the context of %s. ' +
57 | 'Make sure to wrap the top-level component of your app with DragDropContext. ' +
58 | 'Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context',
59 | displayName,
60 | displayName
61 | );
62 |
63 | this.state = this.getCurrentState();
64 | }
65 |
66 | componentDidMount() {
67 | this.isCurrentlyMounted = true;
68 |
69 | const monitor = this.manager.getMonitor();
70 | this.unsubscribeFromOffsetChange = monitor.subscribeToOffsetChange(
71 | this.handleChange
72 | );
73 | this.unsubscribeFromStateChange = monitor.subscribeToStateChange(
74 | this.handleChange
75 | );
76 |
77 | this.handleChange();
78 | }
79 |
80 | componentWillUnmount() {
81 | this.isCurrentlyMounted = false;
82 |
83 | this.unsubscribeFromOffsetChange();
84 | this.unsubscribeFromStateChange();
85 | }
86 |
87 | handleChange() {
88 | if (!this.isCurrentlyMounted) {
89 | return;
90 | }
91 |
92 | const nextState = this.getCurrentState();
93 | if (!shallowEqual(nextState, this.state)) {
94 | this.setState(nextState);
95 | }
96 | }
97 |
98 | getCurrentState() {
99 | const monitor = this.manager.getMonitor();
100 | return collect(monitor);
101 | }
102 |
103 | setChildRef = (child) => {
104 | this.child = child;
105 | }
106 |
107 | render() {
108 | return (
109 |
112 | );
113 | }
114 | }
115 |
116 | return hoistStatics(DragLayerContainer, DecoratedComponent);
117 | };
118 | }
119 |
--------------------------------------------------------------------------------
/src/DragSource.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import isPlainObject from 'lodash/isPlainObject';
3 | import checkDecoratorArguments from './utils/checkDecoratorArguments';
4 | import decorateHandler from './decorateHandler';
5 | import registerSource from './registerSource';
6 | import createSourceFactory from './createSourceFactory';
7 | import createSourceMonitor from './createSourceMonitor';
8 | import createSourceConnector from './createSourceConnector';
9 | import isValidType from './utils/isValidType';
10 |
11 | export default function DragSource(type, spec, collect, options = {}) {
12 | checkDecoratorArguments('DragSource', 'type, spec, collect[, options]', ...arguments);
13 | let getType = type;
14 | if (typeof type !== 'function') {
15 | invariant(
16 | isValidType(type),
17 | 'Expected "type" provided as the first argument to DragSource to be ' +
18 | 'a string, or a function that returns a string given the current props. ' +
19 | 'Instead, received %s. ' +
20 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
21 | type
22 | );
23 | getType = () => type;
24 | }
25 | invariant(
26 | isPlainObject(spec),
27 | 'Expected "spec" provided as the second argument to DragSource to be ' +
28 | 'a plain object. Instead, received %s. ' +
29 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
30 | spec
31 | );
32 | const createSource = createSourceFactory(spec);
33 | invariant(
34 | typeof collect === 'function',
35 | 'Expected "collect" provided as the third argument to DragSource to be ' +
36 | 'a function that returns a plain object of props to inject. ' +
37 | 'Instead, received %s. ' +
38 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
39 | collect
40 | );
41 | invariant(
42 | isPlainObject(options),
43 | 'Expected "options" provided as the fourth argument to DragSource to be ' +
44 | 'a plain object when specified. ' +
45 | 'Instead, received %s. ' +
46 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
47 | collect
48 | );
49 |
50 | return function decorateSource(DecoratedComponent) {
51 | return decorateHandler({
52 | connectBackend: (backend, sourceId) => backend.connectDragSource(sourceId),
53 | containerDisplayName: 'DragSource',
54 | createHandler: createSource,
55 | registerHandler: registerSource,
56 | createMonitor: createSourceMonitor,
57 | createConnector: createSourceConnector,
58 | DecoratedComponent,
59 | getType,
60 | collect,
61 | options
62 | });
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/src/DropTarget.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import isPlainObject from 'lodash/isPlainObject';
3 | import checkDecoratorArguments from './utils/checkDecoratorArguments';
4 | import decorateHandler from './decorateHandler';
5 | import registerTarget from './registerTarget';
6 | import createTargetFactory from './createTargetFactory';
7 | import createTargetMonitor from './createTargetMonitor';
8 | import createTargetConnector from './createTargetConnector';
9 | import isValidType from './utils/isValidType';
10 |
11 | export default function DropTarget(type, spec, collect, options = {}) {
12 | checkDecoratorArguments('DropTarget', 'type, spec, collect[, options]', ...arguments);
13 | let getType = type;
14 | if (typeof type !== 'function') {
15 | invariant(
16 | isValidType(type, true),
17 | 'Expected "type" provided as the first argument to DropTarget to be ' +
18 | 'a string, an array of strings, or a function that returns either given ' +
19 | 'the current props. Instead, received %s. ' +
20 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
21 | type
22 | );
23 | getType = () => type;
24 | }
25 | invariant(
26 | isPlainObject(spec),
27 | 'Expected "spec" provided as the second argument to DropTarget to be ' +
28 | 'a plain object. Instead, received %s. ' +
29 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
30 | spec
31 | );
32 | const createTarget = createTargetFactory(spec);
33 | invariant(
34 | typeof collect === 'function',
35 | 'Expected "collect" provided as the third argument to DropTarget to be ' +
36 | 'a function that returns a plain object of props to inject. ' +
37 | 'Instead, received %s. ' +
38 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
39 | collect
40 | );
41 | invariant(
42 | isPlainObject(options),
43 | 'Expected "options" provided as the fourth argument to DropTarget to be ' +
44 | 'a plain object when specified. ' +
45 | 'Instead, received %s. ' +
46 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
47 | collect
48 | );
49 |
50 | return function decorateTarget(DecoratedComponent) {
51 | return decorateHandler({
52 | connectBackend: (backend, targetId) => backend.connectDropTarget(targetId),
53 | containerDisplayName: 'DropTarget',
54 | createHandler: createTarget,
55 | registerHandler: registerTarget,
56 | createMonitor: createTargetMonitor,
57 | createConnector: createTargetConnector,
58 | DecoratedComponent,
59 | getType,
60 | collect,
61 | options
62 | });
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/src/areOptionsEqual.js:
--------------------------------------------------------------------------------
1 | import shallowEqual from './utils/shallowEqual';
2 |
3 | export default function areOptionsEqual(nextOptions, currentOptions) {
4 | if (currentOptions === nextOptions) {
5 | return true;
6 | }
7 |
8 | return currentOptions !== null &&
9 | nextOptions !== null &&
10 | shallowEqual(currentOptions, nextOptions);
11 | }
--------------------------------------------------------------------------------
/src/createSourceConnector.js:
--------------------------------------------------------------------------------
1 | import wrapConnectorHooks from './wrapConnectorHooks';
2 | import areOptionsEqual from './areOptionsEqual';
3 |
4 | export default function createSourceConnector(backend) {
5 | let currentHandlerId;
6 |
7 | let currentDragSourceNode;
8 | let currentDragSourceOptions;
9 | let disconnectCurrentDragSource;
10 |
11 | let currentDragPreviewNode;
12 | let currentDragPreviewOptions;
13 | let disconnectCurrentDragPreview;
14 |
15 | function reconnectDragSource() {
16 | if (disconnectCurrentDragSource) {
17 | disconnectCurrentDragSource();
18 | disconnectCurrentDragSource = null;
19 | }
20 |
21 | if (currentHandlerId && currentDragSourceNode) {
22 | disconnectCurrentDragSource = backend.connectDragSource(
23 | currentHandlerId,
24 | currentDragSourceNode,
25 | currentDragSourceOptions
26 | );
27 | }
28 | }
29 |
30 | function reconnectDragPreview() {
31 | if (disconnectCurrentDragPreview) {
32 | disconnectCurrentDragPreview();
33 | disconnectCurrentDragPreview = null;
34 | }
35 |
36 | if (currentHandlerId && currentDragPreviewNode) {
37 | disconnectCurrentDragPreview = backend.connectDragPreview(
38 | currentHandlerId,
39 | currentDragPreviewNode,
40 | currentDragPreviewOptions
41 | );
42 | }
43 | }
44 |
45 | function receiveHandlerId(handlerId) {
46 | if (handlerId === currentHandlerId) {
47 | return;
48 | }
49 |
50 | currentHandlerId = handlerId;
51 | reconnectDragSource();
52 | reconnectDragPreview();
53 | }
54 |
55 | const hooks = wrapConnectorHooks({
56 | dragSource: function connectDragSource(node, options) {
57 | if (
58 | node === currentDragSourceNode &&
59 | areOptionsEqual(options, currentDragSourceOptions)
60 | ) {
61 | return;
62 | }
63 |
64 | currentDragSourceNode = node;
65 | currentDragSourceOptions = options;
66 |
67 | reconnectDragSource();
68 | },
69 |
70 | dragPreview: function connectDragPreview(node, options) {
71 | if (
72 | node === currentDragPreviewNode &&
73 | areOptionsEqual(options, currentDragPreviewOptions)
74 | ) {
75 | return;
76 | }
77 |
78 | currentDragPreviewNode = node;
79 | currentDragPreviewOptions = options;
80 |
81 | reconnectDragPreview();
82 | }
83 | });
84 |
85 | return {
86 | receiveHandlerId,
87 | hooks
88 | };
89 | }
--------------------------------------------------------------------------------
/src/createSourceFactory.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import isPlainObject from 'lodash/isPlainObject';
3 |
4 | const ALLOWED_SPEC_METHODS = ['canDrag', 'beginDrag', 'isDragging', 'endDrag'];
5 | const REQUIRED_SPEC_METHODS = ['beginDrag'];
6 |
7 | export default function createSourceFactory(spec) {
8 | Object.keys(spec).forEach(key => {
9 | invariant(
10 | ALLOWED_SPEC_METHODS.indexOf(key) > -1,
11 | 'Expected the drag source specification to only have ' +
12 | 'some of the following keys: %s. ' +
13 | 'Instead received a specification with an unexpected "%s" key. ' +
14 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
15 | ALLOWED_SPEC_METHODS.join(', '),
16 | key
17 | );
18 | invariant(
19 | typeof spec[key] === 'function',
20 | 'Expected %s in the drag source specification to be a function. ' +
21 | 'Instead received a specification with %s: %s. ' +
22 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
23 | key,
24 | key,
25 | spec[key]
26 | );
27 | });
28 | REQUIRED_SPEC_METHODS.forEach(key => {
29 | invariant(
30 | typeof spec[key] === 'function',
31 | 'Expected %s in the drag source specification to be a function. ' +
32 | 'Instead received a specification with %s: %s. ' +
33 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
34 | key,
35 | key,
36 | spec[key]
37 | );
38 | });
39 |
40 | class Source {
41 | constructor(monitor) {
42 | this.monitor = monitor;
43 | this.props = null;
44 | this.component = null;
45 | }
46 |
47 | receiveProps(props) {
48 | this.props = props;
49 | }
50 |
51 | receiveComponent(component) {
52 | this.component = component;
53 | }
54 |
55 | canDrag() {
56 | if (!spec.canDrag) {
57 | return true;
58 | }
59 |
60 | return spec.canDrag(this.props, this.monitor);
61 | }
62 |
63 | isDragging(globalMonitor, sourceId) {
64 | if (!spec.isDragging) {
65 | return sourceId === globalMonitor.getSourceId();
66 | }
67 |
68 | return spec.isDragging(this.props, this.monitor);
69 | }
70 |
71 | beginDrag() {
72 | const item = spec.beginDrag(this.props, this.monitor, this.component);
73 | if (process.env.NODE_ENV !== 'production') {
74 | invariant(
75 | isPlainObject(item),
76 | 'beginDrag() must return a plain object that represents the dragged item. ' +
77 | 'Instead received %s. ' +
78 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source.html',
79 | item
80 | );
81 | }
82 | return item;
83 | }
84 |
85 | endDrag() {
86 | if (!spec.endDrag) {
87 | return;
88 | }
89 |
90 | spec.endDrag(this.props, this.monitor, this.component);
91 | }
92 | }
93 |
94 | return function createSource(monitor) {
95 | return new Source(monitor);
96 | };
97 | }
98 |
--------------------------------------------------------------------------------
/src/createSourceMonitor.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 |
3 | let isCallingCanDrag = false;
4 | let isCallingIsDragging = false;
5 |
6 | class SourceMonitor {
7 | constructor(manager) {
8 | this.internalMonitor = manager.getMonitor();
9 | }
10 |
11 | receiveHandlerId(sourceId) {
12 | this.sourceId = sourceId;
13 | }
14 |
15 | canDrag() {
16 | invariant(
17 | !isCallingCanDrag,
18 | 'You may not call monitor.canDrag() inside your canDrag() implementation. ' +
19 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html'
20 | );
21 |
22 | try {
23 | isCallingCanDrag = true;
24 | return this.internalMonitor.canDragSource(this.sourceId);
25 | } finally {
26 | isCallingCanDrag = false;
27 | }
28 | }
29 |
30 | isDragging() {
31 | invariant(
32 | !isCallingIsDragging,
33 | 'You may not call monitor.isDragging() inside your isDragging() implementation. ' +
34 | 'Read more: http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html'
35 | );
36 |
37 | try {
38 | isCallingIsDragging = true;
39 | return this.internalMonitor.isDraggingSource(this.sourceId);
40 | } finally {
41 | isCallingIsDragging = false;
42 | }
43 | }
44 |
45 | getItemType() {
46 | return this.internalMonitor.getItemType();
47 | }
48 |
49 | getItem() {
50 | return this.internalMonitor.getItem();
51 | }
52 |
53 | getDropResult() {
54 | return this.internalMonitor.getDropResult();
55 | }
56 |
57 | didDrop() {
58 | return this.internalMonitor.didDrop();
59 | }
60 |
61 | getInitialClientOffset() {
62 | return this.internalMonitor.getInitialClientOffset();
63 | }
64 |
65 | getInitialSourceClientOffset() {
66 | return this.internalMonitor.getInitialSourceClientOffset();
67 | }
68 |
69 | getSourceClientOffset() {
70 | return this.internalMonitor.getSourceClientOffset();
71 | }
72 |
73 | getClientOffset() {
74 | return this.internalMonitor.getClientOffset();
75 | }
76 |
77 | getDifferenceFromInitialOffset() {
78 | return this.internalMonitor.getDifferenceFromInitialOffset();
79 | }
80 | }
81 |
82 | export default function createSourceMonitor(manager) {
83 | return new SourceMonitor(manager);
84 | }
--------------------------------------------------------------------------------
/src/createTargetConnector.js:
--------------------------------------------------------------------------------
1 | import wrapConnectorHooks from './wrapConnectorHooks';
2 | import areOptionsEqual from './areOptionsEqual';
3 |
4 | export default function createTargetConnector(backend) {
5 | let currentHandlerId;
6 |
7 | let currentDropTargetNode;
8 | let currentDropTargetOptions;
9 | let disconnectCurrentDropTarget;
10 |
11 | function reconnectDropTarget() {
12 | if (disconnectCurrentDropTarget) {
13 | disconnectCurrentDropTarget();
14 | disconnectCurrentDropTarget = null;
15 | }
16 |
17 | if (currentHandlerId && currentDropTargetNode) {
18 | disconnectCurrentDropTarget = backend.connectDropTarget(
19 | currentHandlerId,
20 | currentDropTargetNode,
21 | currentDropTargetOptions
22 | );
23 | }
24 | }
25 |
26 | function receiveHandlerId(handlerId) {
27 | if (handlerId === currentHandlerId) {
28 | return;
29 | }
30 |
31 | currentHandlerId = handlerId;
32 | reconnectDropTarget();
33 | }
34 |
35 | const hooks = wrapConnectorHooks({
36 | dropTarget: function connectDropTarget(node, options) {
37 | if (
38 | node === currentDropTargetNode &&
39 | areOptionsEqual(options, currentDropTargetOptions)
40 | ) {
41 | return;
42 | }
43 |
44 | currentDropTargetNode = node;
45 | currentDropTargetOptions = options;
46 |
47 | reconnectDropTarget();
48 | }
49 | });
50 |
51 | return {
52 | receiveHandlerId,
53 | hooks
54 | };
55 | }
--------------------------------------------------------------------------------
/src/createTargetFactory.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import isPlainObject from 'lodash/isPlainObject';
3 |
4 | const ALLOWED_SPEC_METHODS = ['canDrop', 'hover', 'drop'];
5 |
6 | export default function createTargetFactory(spec) {
7 | Object.keys(spec).forEach(key => {
8 | invariant(
9 | ALLOWED_SPEC_METHODS.indexOf(key) > -1,
10 | 'Expected the drop target specification to only have ' +
11 | 'some of the following keys: %s. ' +
12 | 'Instead received a specification with an unexpected "%s" key. ' +
13 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
14 | ALLOWED_SPEC_METHODS.join(', '),
15 | key
16 | );
17 | invariant(
18 | typeof spec[key] === 'function',
19 | 'Expected %s in the drop target specification to be a function. ' +
20 | 'Instead received a specification with %s: %s. ' +
21 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
22 | key,
23 | key,
24 | spec[key]
25 | );
26 | });
27 |
28 | class Target {
29 | constructor(monitor) {
30 | this.monitor = monitor;
31 | this.props = null;
32 | this.component = null;
33 | }
34 |
35 | receiveProps(props) {
36 | this.props = props;
37 | }
38 |
39 | receiveMonitor(monitor) {
40 | this.monitor = monitor;
41 | }
42 |
43 | receiveComponent(component) {
44 | this.component = component;
45 | }
46 |
47 | canDrop() {
48 | if (!spec.canDrop) {
49 | return true;
50 | }
51 |
52 | return spec.canDrop(this.props, this.monitor);
53 | }
54 |
55 | hover() {
56 | if (!spec.hover) {
57 | return;
58 | }
59 |
60 | spec.hover(this.props, this.monitor, this.component);
61 | }
62 |
63 | drop() {
64 | if (!spec.drop) {
65 | return;
66 | }
67 |
68 | const dropResult = spec.drop(this.props, this.monitor, this.component);
69 | if (process.env.NODE_ENV !== 'production') {
70 | invariant(
71 | typeof dropResult === 'undefined' ||
72 | isPlainObject(dropResult),
73 | 'drop() must either return undefined, or an object that represents the drop result. ' +
74 | 'Instead received %s. ' +
75 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target.html',
76 | dropResult
77 | );
78 | }
79 | return dropResult;
80 | }
81 | }
82 |
83 | return function createTarget(monitor) {
84 | return new Target(monitor);
85 | };
86 | }
87 |
--------------------------------------------------------------------------------
/src/createTargetMonitor.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 |
3 | let isCallingCanDrop = false;
4 |
5 | class TargetMonitor {
6 | constructor(manager) {
7 | this.internalMonitor = manager.getMonitor();
8 | }
9 |
10 | receiveHandlerId(targetId) {
11 | this.targetId = targetId;
12 | }
13 |
14 | canDrop() {
15 | invariant(
16 | !isCallingCanDrop,
17 | 'You may not call monitor.canDrop() inside your canDrop() implementation. ' +
18 | 'Read more: http://gaearon.github.io/react-dnd/docs-drop-target-monitor.html'
19 | );
20 |
21 | try {
22 | isCallingCanDrop = true;
23 | return this.internalMonitor.canDropOnTarget(this.targetId);
24 | } finally {
25 | isCallingCanDrop = false;
26 | }
27 | }
28 |
29 | isOver(options) {
30 | return this.internalMonitor.isOverTarget(this.targetId, options);
31 | }
32 |
33 | getItemType() {
34 | return this.internalMonitor.getItemType();
35 | }
36 |
37 | getItem() {
38 | return this.internalMonitor.getItem();
39 | }
40 |
41 | getDropResult() {
42 | return this.internalMonitor.getDropResult();
43 | }
44 |
45 | didDrop() {
46 | return this.internalMonitor.didDrop();
47 | }
48 |
49 | getInitialClientOffset() {
50 | return this.internalMonitor.getInitialClientOffset();
51 | }
52 |
53 | getInitialSourceClientOffset() {
54 | return this.internalMonitor.getInitialSourceClientOffset();
55 | }
56 |
57 | getSourceClientOffset() {
58 | return this.internalMonitor.getSourceClientOffset();
59 | }
60 |
61 | getClientOffset() {
62 | return this.internalMonitor.getClientOffset();
63 | }
64 |
65 | getDifferenceFromInitialOffset() {
66 | return this.internalMonitor.getDifferenceFromInitialOffset();
67 | }
68 | }
69 |
70 | export default function createTargetMonitor(manager) {
71 | return new TargetMonitor(manager);
72 | }
--------------------------------------------------------------------------------
/src/decorateHandler.js:
--------------------------------------------------------------------------------
1 | import preact, { Component } from 'preact';
2 | import { Disposable, CompositeDisposable, SerialDisposable } from 'disposables';
3 | import shallowEqual from './utils/shallowEqual';
4 | import shallowEqualScalar from './utils/shallowEqualScalar';
5 | import isPlainObject from 'lodash/isPlainObject';
6 | import invariant from 'invariant';
7 | import hoistStatics from 'hoist-non-react-statics';
8 |
9 | export default function decorateHandler({
10 | DecoratedComponent,
11 | createHandler,
12 | createMonitor,
13 | createConnector,
14 | registerHandler,
15 | containerDisplayName,
16 | getType,
17 | collect,
18 | options
19 | }) {
20 | const { arePropsEqual = shallowEqualScalar } = options;
21 | const displayName =
22 | DecoratedComponent.displayName ||
23 | DecoratedComponent.name ||
24 | 'Component';
25 |
26 | class DragDropContainer extends Component {
27 | static DecoratedComponent = DecoratedComponent;
28 |
29 | static displayName = `${containerDisplayName}(${displayName})`;
30 |
31 | getHandlerId() {
32 | return this.handlerId;
33 | }
34 |
35 | getDecoratedComponentInstance() {
36 | return this.decoratedComponentInstance;
37 | }
38 |
39 | shouldComponentUpdate(nextProps, nextState) {
40 | return !arePropsEqual(nextProps, this.props) ||
41 | !shallowEqual(nextState, this.state);
42 | }
43 |
44 | constructor(props, context) {
45 | super(props, context);
46 | this.handleChange = this.handleChange.bind(this);
47 | this.handleChildRef = this.handleChildRef.bind(this);
48 |
49 | invariant(
50 | typeof this.context.dragDropManager === 'object',
51 | 'Could not find the drag and drop manager in the context of %s. ' +
52 | 'Make sure to wrap the top-level component of your app with DragDropContext. ' +
53 | 'Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context',
54 | displayName,
55 | displayName
56 | );
57 |
58 | this.manager = this.context.dragDropManager;
59 | this.handlerMonitor = createMonitor(this.manager);
60 | this.handlerConnector = createConnector(this.manager.getBackend());
61 | this.handler = createHandler(this.handlerMonitor);
62 |
63 | this.disposable = new SerialDisposable();
64 | this.receiveProps(props);
65 | this.state = this.getCurrentState();
66 | this.dispose();
67 | }
68 |
69 | componentDidMount() {
70 | this.isCurrentlyMounted = true;
71 | this.disposable = new SerialDisposable();
72 | this.currentType = null;
73 | this.receiveProps(this.props);
74 | this.handleChange();
75 | }
76 |
77 | componentWillReceiveProps(nextProps) {
78 | if (!arePropsEqual(nextProps, this.props)) {
79 | this.receiveProps(nextProps);
80 | this.handleChange();
81 | }
82 | }
83 |
84 | componentWillUnmount() {
85 | this.dispose();
86 | this.isCurrentlyMounted = false;
87 | }
88 |
89 | receiveProps(props) {
90 | this.handler.receiveProps(props);
91 | this.receiveType(getType(props));
92 | }
93 |
94 | receiveType(type) {
95 | if (type === this.currentType) {
96 | return;
97 | }
98 |
99 | this.currentType = type;
100 |
101 | const {
102 | handlerId,
103 | unregister
104 | } = registerHandler(
105 | type,
106 | this.handler,
107 | this.manager
108 | );
109 |
110 | this.handlerId = handlerId;
111 | this.handlerMonitor.receiveHandlerId(handlerId);
112 | this.handlerConnector.receiveHandlerId(handlerId);
113 |
114 | const globalMonitor = this.manager.getMonitor();
115 | const unsubscribe = globalMonitor.subscribeToStateChange(
116 | this.handleChange,
117 | { handlerIds: [handlerId] }
118 | );
119 |
120 | this.disposable.setDisposable(
121 | new CompositeDisposable(
122 | new Disposable(unsubscribe),
123 | new Disposable(unregister)
124 | )
125 | );
126 | }
127 |
128 | handleChange() {
129 | if (!this.isCurrentlyMounted) {
130 | return;
131 | }
132 |
133 | const nextState = this.getCurrentState();
134 | if (!shallowEqual(nextState, this.state)) {
135 | this.setState(nextState);
136 | }
137 | }
138 |
139 | dispose() {
140 | this.disposable.dispose();
141 | this.handlerConnector.receiveHandlerId(null);
142 | }
143 |
144 | handleChildRef(component) {
145 | this.decoratedComponentInstance = component;
146 | this.handler.receiveComponent(component);
147 | }
148 |
149 | getCurrentState() {
150 | const nextState = collect(
151 | this.handlerConnector.hooks,
152 | this.handlerMonitor
153 | );
154 |
155 | if (process.env.NODE_ENV !== 'production') {
156 | invariant(
157 | isPlainObject(nextState),
158 | 'Expected `collect` specified as the second argument to ' +
159 | '%s for %s to return a plain object of props to inject. ' +
160 | 'Instead, received %s.',
161 | containerDisplayName,
162 | displayName,
163 | nextState
164 | );
165 | }
166 |
167 | return nextState;
168 | }
169 |
170 | render() {
171 | return (
172 |
175 | );
176 | }
177 | }
178 |
179 | return hoistStatics(DragDropContainer, DecoratedComponent);
180 | }
181 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as DragDropContext } from './DragDropContext';
2 | export { default as DragLayer } from './DragLayer';
3 | export { default as DragSource } from './DragSource';
4 | export { default as DropTarget } from './DropTarget';
5 |
--------------------------------------------------------------------------------
/src/registerSource.js:
--------------------------------------------------------------------------------
1 | export default function registerSource(type, source, manager) {
2 | const registry = manager.getRegistry();
3 | const sourceId = registry.addSource(type, source);
4 |
5 | function unregisterSource() {
6 | registry.removeSource(sourceId);
7 | }
8 |
9 | return {
10 | handlerId: sourceId,
11 | unregister: unregisterSource
12 | };
13 | }
--------------------------------------------------------------------------------
/src/registerTarget.js:
--------------------------------------------------------------------------------
1 | export default function registerTarget(type, target, manager) {
2 | const registry = manager.getRegistry();
3 | const targetId = registry.addTarget(type, target);
4 |
5 | function unregisterTarget() {
6 | registry.removeTarget(targetId);
7 | }
8 |
9 | return {
10 | handlerId: targetId,
11 | unregister: unregisterTarget
12 | };
13 | }
--------------------------------------------------------------------------------
/src/utils/checkDecoratorArguments.js:
--------------------------------------------------------------------------------
1 | export default function checkDecoratorArguments(functionName, signature, ...args) {
2 | if (process.env.NODE_ENV !== 'production') {
3 | for (let i = 0; i < args.length; i++) {
4 | const arg = args[i];
5 | if (arg && arg.prototype && arg.prototype.render) {
6 | console.error( // eslint-disable-line no-console
7 | `You seem to be applying the arguments in the wrong order. ` +
8 | `It should be ${functionName}(${signature})(Component), not the other way around. ` +
9 | `Read more: http://gaearon.github.io/react-dnd/docs-troubleshooting.html#you-seem-to-be-applying-the-arguments-in-the-wrong-order`
10 | );
11 | return;
12 | }
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/utils/cloneWithRef.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import { cloneElement } from 'preact';
3 |
4 | export default function cloneWithRef(element, newRef) {
5 | const previousRef = element.attributes.ref;
6 | invariant(
7 | typeof previousRef !== 'string',
8 | 'Cannot connect React DnD to an element with an existing string ref. ' +
9 | 'Please convert it to use a callback ref instead, or wrap it into a or . ' +
10 | 'Read more: https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute'
11 | );
12 |
13 | if (!previousRef) {
14 | // When there is no ref on the element, use the new ref directly
15 | return cloneElement(element, {
16 | ref: newRef
17 | });
18 | }
19 |
20 | return cloneElement(element, {
21 | ref: (node) => {
22 | newRef(node);
23 |
24 | if (previousRef) {
25 | previousRef(node);
26 | }
27 | }
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/utils/isValidType.js:
--------------------------------------------------------------------------------
1 | import isArray from 'lodash/isArray';
2 |
3 | export default function isValidType(type, allowArray) {
4 | return typeof type === 'string' ||
5 | typeof type === 'symbol' ||
6 | allowArray && isArray(type) && type.every(t => isValidType(t, false));
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/shallowEqual.js:
--------------------------------------------------------------------------------
1 | export default function shallowEqual(objA, objB) {
2 | if (objA === objB) {
3 | return true;
4 | }
5 |
6 | const keysA = Object.keys(objA);
7 | const keysB = Object.keys(objB);
8 |
9 | if (keysA.length !== keysB.length) {
10 | return false;
11 | }
12 |
13 | // Test for A's keys different from B.
14 | const hasOwn = Object.prototype.hasOwnProperty;
15 | for (let i = 0; i < keysA.length; i++) {
16 | if (!hasOwn.call(objB, keysA[i]) ||
17 | objA[keysA[i]] !== objB[keysA[i]]) {
18 | return false;
19 | }
20 |
21 | const valA = objA[keysA[i]];
22 | const valB = objB[keysA[i]];
23 |
24 | if (valA !== valB) {
25 | return false;
26 | }
27 | }
28 |
29 | return true;
30 | }
--------------------------------------------------------------------------------
/src/utils/shallowEqualScalar.js:
--------------------------------------------------------------------------------
1 | export default function shallowEqualScalar(objA, objB) {
2 | if (objA === objB) {
3 | return true;
4 | }
5 |
6 | if (typeof objA !== 'object' || objA === null ||
7 | typeof objB !== 'object' || objB === null) {
8 | return false;
9 | }
10 |
11 | const keysA = Object.keys(objA);
12 | const keysB = Object.keys(objB);
13 |
14 | if (keysA.length !== keysB.length) {
15 | return false;
16 | }
17 |
18 | // Test for A's keys different from B.
19 | const hasOwn = Object.prototype.hasOwnProperty;
20 | for (let i = 0; i < keysA.length; i++) {
21 | if (!hasOwn.call(objB, keysA[i])) {
22 | return false;
23 | }
24 |
25 | const valA = objA[keysA[i]];
26 | const valB = objB[keysA[i]];
27 |
28 | if (valA !== valB || typeof valA === 'object' || typeof valB === 'object') {
29 | return false;
30 | }
31 | }
32 |
33 | return true;
34 | }
--------------------------------------------------------------------------------
/src/wrapConnectorHooks.js:
--------------------------------------------------------------------------------
1 | import cloneWithRef from './utils/cloneWithRef';
2 | // import { isValidElement } from 'react';
3 |
4 | function isValidElement(object) {
5 | if (!object) return false;
6 |
7 | return (
8 | 'nodeName' in object &&
9 | 'attributes' in object &&
10 | 'children' in object &&
11 | 'key' in object
12 | );
13 | }
14 |
15 | function throwIfCompositeComponentElement(element) {
16 | // Custom components can no longer be wrapped directly in React DnD 2.0
17 | // so that we don't need to depend on findDOMNode() from react-dom.
18 | if (typeof element.nodeName === 'string') {
19 | return;
20 | }
21 |
22 | const displayName = element.nodeName.displayName ||
23 | element.nodeName.name ||
24 | 'the component';
25 |
26 | throw new Error(
27 | `Only native element nodes can now be passed to React DnD connectors. ` +
28 | `You can either wrap ${displayName} into a
, or turn it into a ` +
29 | `drag source or a drop target itself.`
30 | );
31 | }
32 |
33 | function wrapHookToRecognizeElement(hook) {
34 | return (elementOrNode = null, options = null) => {
35 | // When passed a node, call the hook straight away.
36 | if (!isValidElement(elementOrNode)) {
37 | const node = elementOrNode;
38 | hook(node, options);
39 | return;
40 | }
41 |
42 | // If passed a ReactElement, clone it and attach this function as a ref.
43 | // This helps us achieve a neat API where user doesn't even know that refs
44 | // are being used under the hood.
45 | const element = elementOrNode;
46 | throwIfCompositeComponentElement(element);
47 |
48 | // When no options are passed, use the hook directly
49 | const ref = options ?
50 | node => hook(node, options) :
51 | hook;
52 |
53 | return cloneWithRef(element, ref);
54 | };
55 | }
56 |
57 | export default function wrapConnectorHooks(hooks) {
58 | const wrappedHooks = {};
59 |
60 | Object.keys(hooks).forEach(key => {
61 | const hook = hooks[key];
62 | const wrappedHook = wrapHookToRecognizeElement(hook);
63 | wrappedHooks[key] = () => wrappedHook;
64 | });
65 |
66 | return wrappedHooks;
67 | }
--------------------------------------------------------------------------------
/test-utils.js:
--------------------------------------------------------------------------------
1 | import preact from 'preact';
2 |
3 | export default {
4 | renderIntoDocument(component, callback) {
5 | const elem = document.createElement('div');
6 |
7 | class Wrap extends preact.Component {
8 | render() {
9 | const clone = preact.cloneElement(component, {
10 | ref: (a) => {
11 | this.instance = a;
12 | }
13 | });
14 |
15 | return clone;
16 | }
17 |
18 | componentDidMount() {
19 | callback(this.instance);
20 | }
21 | }
22 |
23 | preact.render( , elem);
24 | }
25 | };
--------------------------------------------------------------------------------
/tests.webpack.js:
--------------------------------------------------------------------------------
1 | var context = require.context('./examples', true, /-test\.js$/);
2 | context.keys().forEach(context);
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | module.exports = {
4 | entry: './src/index',
5 | module: {
6 | loaders: [
7 | { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }
8 | ]
9 | },
10 | externals: [{
11 | preact: {
12 | root: 'preact',
13 | commonjs2: 'preact',
14 | commonjs: 'preact',
15 | amd: 'preact'
16 | }
17 | }],
18 | output: {
19 | filename: 'dist/ReactDnD.min.js',
20 | libraryTarget: 'umd',
21 | library: 'ReactDnD'
22 | },
23 | plugins: [
24 | new webpack.optimize.OccurenceOrderPlugin(),
25 | new webpack.DefinePlugin({
26 | 'process.env': {
27 | 'NODE_ENV': JSON.stringify('production')
28 | }
29 | })
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------