Added logging, changed some directory structure

This commit is contained in:
2018-01-13 21:33:40 -05:00
parent f079a5f067
commit 8e72ffb917
73656 changed files with 35284 additions and 53718 deletions

View File

@@ -0,0 +1,363 @@
# Changelog
### 3.0.3 (Aug 31, 2017)
- Bugfix: Fix deprecation warnings caused by `import * as React` (Flow best practice).
- See https://github.com/facebook/react/issues/10583
### 3.0.2 (Aug 22, 2017)
> 3.0.0 and 3.0.1 have been unpublished due to a large logfile making it into the package.
- Bugfix: Tweaked `.npmignore`.
### 3.0.1 (Aug 21, 2017)
- Bugfix: Flow-type should no longer throw errors for consumers.
- It appears Flow can't resolve a sub-package's interfaces.
### 3.0.0 (Aug 21, 2017)
> Due to an export change, this is semver-major.
- Breaking: For TypeScript users, `<Draggable>` is now exported as `module.exports` and `module.exports.default`.
- Potentially Breaking: We no longer set `user-select: none` on all elements while dragging. Instead,
the [`::selection` psuedo element](https://developer.mozilla.org/en-US/docs/Web/CSS/::selection) is used.
- Depending on your application, this could cause issues, so be sure to test.
- Bugfix: Pass bounded `x`/`y` to callbacks. See [#226](https://github.com/mzabriskie/react-draggable/pull/226).
- Internal: Upgraded dependencies.
### 2.2.6 (Apr 30, 2017)
- Bugfix: Missing export default on TS definition (thanks @lostfictions)
- Internal: TS test suite (thanks @lostfictions)
### 2.2.5 (Apr 28, 2017)
- Bugfix: Typescript definition was incorrect. [#244](https://github.com/mzabriskie/react-draggable/issues/244)
### 2.2.4 (Apr 27, 2017)
- Internal: Moved `PropTypes` access to `prop-types` package for React 15.5 (prep for 16)
- Feature: Added TypeScript definitions (thanks @erfangc)
- Bugfix: No longer can erroneously add user-select style multiple times
- Bugfix: OffsetParent with padding problem, fixes [#218](https://github.com/mzabriskie/react-draggable/issues/218)
- Refactor: Misc example updates.
### 2.2.3 (Nov 21, 2016)
- Bugfix: Fix an issue with the entire window scrolling on a drag on iDevices. Thanks @JaneCoder. See #183
### 2.2.2 (Sep 14, 2016)
- Bugfix: Fix references to global when grabbing `SVGElement`, see [#162](https://github.com/mzabriskie/react-draggable/issues/162)
- Bugfix: Get `ownerDocument` before `onStop`, fixes [#198](https://github.com/mzabriskie/react-draggable/issues/198)
### 2.2.1 (Aug 11, 2016)
- Bugfix: Fix `getComputedStyle` error: see [#186](https://github.com/mzabriskie/react-draggable/issues/186), #190
### 2.2.0 (Jul 29, 2016)
- Addition: `offsetParent` property for an arbitrary ancestor for offset calculations.
- Fixes e.g. dragging with a floating `offsetParent`.
- Ref: https://github.com/mzabriskie/react-draggable/issues/170
- Enhancement: Make this library iframe-aware.
- Ref: https://github.com/mzabriskie/react-draggable/pull/177
- Thanks to @acusti for tests
- Bugfix: Lint/Test Fixes for new Flow & React versions
### 2.1.2 (Jun 5, 2016)
- Bugfix: Fix `return false` to cancel `onDrag` breaking on both old and new browsers due to missing `typeArg` and/or
unsupported `MouseEventConstructor`. Fixes [#164](https://github.com/mzabriskie/react-draggable/issues/164).
### 2.1.1 (May 22, 2016)
- Bugfix: `<DraggableCore>` wasn't calling back with the DOM node.
- Internal: Rework test suite to use power-assert.
### 2.1.0 (May 20, 2016)
- Fix improperly missed `handle` or `cancel` selectors if the event originates from a child of the handle or cancel.
- Fixes a longstanding issue, [#88](https://github.com/mzabriskie/react-draggable/pull/88)
- This was pushed to a minor release as there may be edge cases (perhaps workarounds) where this changes behavior.
### 2.0.2 (May 19, 2016)
- Fix `cannot access clientX of undefined` on some touch-enabled platforms.
- Fixes [#159](https://github.com/mzabriskie/react-draggable/pull/159),
[#118](https://github.com/mzabriskie/react-draggable/pull/118)
- Fixed a bug with multi-finger multitouch if > 1 finger triggered an event at the same time.
### 2.0.1 (May 19, 2016)
- Finally fixed the IE10 constructor bug. Thanks @davidstubbs [#158](https://github.com/mzabriskie/react-draggable/pull/158)
### 2.0.0 (May 10, 2016)
- This is a breaking change. See the changes below in the beta releases.
- Note the changes to event callbacks and `position` / `defaultPosition`.
- Changes from 2.0.0-beta3:
- Small bugfixes for Flow 0.24 compatibility.
- Don't assume `global.SVGElement`. Fixes JSDOM & [#123](https://github.com/mzabriskie/react-draggable/issues/123).
### 2.0.0-beta3 (Apr 19, 2016)
- Flow comments are now in the build. Other projects, such as React-Grid-Layout and React-Resizable, will
rely on them in their build and export their own comments.
### 2.0.0-beta2 (Apr 14, 2016)
- We're making a small deviation from React Core's controlled vs. uncontrolled scheme; for convenience,
`<Draggable>`s with a `position` property will still be draggable, but will revert to their old position
on drag stop. Attach an `onStop` or `onDrag` handler to synchronize state.
- A warning has been added informing users of this. If you make `<Draggable>` controlled but attach no callback
handlers, a warning will be printed.
### 2.0.0-beta1 (Apr 14, 2016)
- Due to API changes, this is a major release.
#### Breaking Changes:
- Both `<DraggableCore>` and `<Draggable>` have had their callback types changed and unified.
```js
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
node: HTMLElement,
// lastX + deltaX === x
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
};
```
- The `start` option has been renamed to `defaultPosition`.
- The `zIndex` option has been removed.
#### Possibly Breaking Changes:
- When determining deltas, we now use a new method that checks the delta against the Draggable's `offsetParent`.
This method allows us to support arbitrary nested scrollable ancestors without scroll handlers!
- This may cause issues in certain layouts. If you find one, please open an issue.
#### Enhancements:
- `<Draggable>` now has a `position` attribute. Its relationship to `defaultPosition` is much like
`value` to `defaultValue` on React `<input>` nodes. If set, the position is fixed and cannot be mutated.
If empty, the component will manage its own state. See [#140](https://github.com/mzabriskie/react-draggable/pull/140)
for more info & motivations.
- Misc. bugfixes.
### 1.4.0-beta1 (Apr 13, 2016)
- Major improvements to drag tracking that now support even nested scroll boxes.
- This revision is being done as a pre-release to ensure there are no unforeseen issues with the offset changes.
### 1.3.7 (Apr 8, 2016)
- Fix `user-select` prefixing, which may be different than the prefix required for `transform`.
### 1.3.6 (Apr 8, 2016)
- Republished after 1.3.5 contained a bundling error.
### 1.3.5 (Apr 8, 2016)
- Add React v15 to devDeps. `<Draggable>` supports both `v0.14` and `v15`.
- Enhancement: Clean up usage of browser prefixes; modern browsers will no longer use them.
- This also removes the duplicated `user-select` style that is created on the `<body>` while dragging.
- Internal: Test fixes.
### 1.3.4 (Mar 5, 2016)
- Bugfix: Scrolling while dragging caused items to move unpredictably.
### 1.3.3 (Feb 11, 2016)
- Bugfix: #116: Android/Chrome are finicky; give up on canceling ghost clicks entirely.
### 1.3.2 (Feb 11, 2016)
- Bugfix: #116: Child inputs not focusing on touch events.
### 1.3.1 (Feb 10, 2016)
- Internal: Babel 6 and Flow definitions
- Bugfix: 1.3.0 broke string bounds ('parent', selectors, etc.).
- Bugfix: 1.3.0 wasn't updating deltaX and deltaY on a bounds hit.
### 1.3.0 (Feb 10, 2016)
- Possibly breaking change: bounds are calculated before `<Draggable>` fires `drag` events, as they should have been.
- Added `'none'` axis type. This allows using `<Draggable>` somewhat like `<DraggableCore>` - state will be kept
internally (which makes bounds checks etc possible), but updates will not be flushed to the DOM.
- Performance tweaks.
### 1.2.0 (Feb 5, 2016)
- Added arbitrary boundary selector. Now you don't have to just use `'parent'`, you can select any element
on the page, including `'body'`.
- Bugfix: Prevent invariant if a `<Draggable>` is unmounted while dragging.
- Bugfix: Fix #133, where items would eagerly start dragging off the mouse cursor if you hit boundaries and
came back. This is due to how `<DraggableCore>` handles deltas only and does not keep state. Added new state
properties `slackX` and `slackY` to `<Draggable>` to handle this and restore pre-v1 behavior.
### 1.1.3 (Nov 25, 2015)
- Bugfix: Server-side rendering with react-rails, which does bad things like mock `window`
### 1.1.2 (Nov 23, 2015)
- Bugfix: `<Draggable>` was calling back with clientX/Y, not offsetX/Y as it did pre-1.0. This unintended
behavior has been fixed and a test has been added.
### 1.1.1 (Nov 14, 2015)
- Bugfix: Clean up scroll events if a component is unmounted before drag stops.
- Bugfix: `NaN` was returning from scroll events due to event structure change.
- Add scroll drag modulation test.
### 1.1.0 (Nov 14, 2015)
- Move `grid` into `<DraggableCore>` directly. It will continue to work on `<Draggable>`.
- Development fixes.
### 1.0.2 (Nov 7, 2015)
- Fix `enableUserSelectHack` not properly disabling.
- Fix a crash when the user scrolls the page with a Draggable active.
### 1.0.1 (Oct 28, 2015)
- Fix missing dist files for webpack.
- Ignore non-primary clicks. Added `allowAnyClick` option to allow other click types.
### 1.0.0 (Oct 27, 2015)
- Breaking: Removed `resetState()` instance method
- Breaking: Removed `moveOnStartChange` prop
- Breaking: React `0.14` support only.
- Refactored project.
- Module now exports a `<DraggableCore>` element upon which `<Draggable>` is based.
This module is useful for building libraries and is completely stateless.
### 0.8.5 (Oct 20, 2015)
- Bugfix: isElementSVG no longer can be overwritten by getInitialState (#83)
- Bugfix: Fix for element prefixes in JSDOM
### 0.8.4 (Oct 15, 2015)
- Bugfix: SVG elements now properly use `transform` attribute instead of `style`. Thanks @martinRoss
### 0.8.3 (Oct 12, 2015)
- Bugfix: Short-circuiting drag throws due to `e.changedTouches` check.
### 0.8.2 (Sep 21, 2015)
- Handle scrolling while dragging. (#60)
- Add multi-touch support. (#68)
- IE fixes.
- Documentation updates. (#77)
### 0.8.1 (June 3, 2015)
- Add `resetState()` instance method for use by parents. See README ("State Problems?").
### 0.8.0 (May 19, 2015)
- Touch/mouse events rework. Fixes #51, #37, and #43, as well as IE11 support.
- Moved mousemove/mouseup and touch event handlers to document from window. Fixes IE9/10 support.
IE8 is still not supported, as it is not supported by React.
### 0.7.4 (May 18, 2015)
- Fix a bug where a quick drag out of bounds to `0,0` would cause the element to remain in an inaccurate position,
because the translation was removed from the CSS. See #55.
### 0.7.3 (May 13, 2015)
- Removed a `moveOnStartChange` optimization that was causing problems when attempting to move a `<Draggable>` back
to its initial position. See https://github.com/STRML/react-grid-layout/issues/56
### 0.7.2 (May 8, 2015)
- Added `moveOnStartChange` property. See README.
### 0.7.1 (May 7, 2015)
- The `start` param is back. Pass `{x: Number, y: Number}` to kickoff the CSS transform. Useful in certain
cases for simpler callback math (so you don't have to know its existing relative position and add it to
the dragged position). Fixes #52.
### 0.7.0 (May 7, 2015)
- Breaking change: `bounds` with coordinates was confusing because it was using the item's width/height,
which was not intuitive. When providing coordinates, `bounds` now simply restricts movement in each
direction by that many pixels.
### 0.6.0 (May 2, 2015)
- Breaking change: Cancel dragging when onDrag or onStart handlers return an explicit `false`.
- Fix sluggish movement when `grid` option was active.
- Example updates.
- Move `user-select:none` hack to document.body for better highlight prevention.
- Add `bounds` option to restrict dragging within parent or within coordinates.
### 0.5.0 (May 2, 2015)
- Remove browserify browser config, reactify, and jsx pragma. Fixes #38
- Use React.cloneElement instead of addons cloneWithProps (requires React 0.13)
- Move to CSS transforms. Simplifies implementation and fixes #48, #34, #31.
- Fixup linting and space/tab errors. Fixes #46.
### 0.4.3 (Apr 30, 2015)
- Fix React.addons error caused by faulty test.
### 0.4.2 (Apr 30, 2015)
- Add `"browser"` config to package.json for browserify imports (fix #45).
- Remove unnecessary `emptyFunction` and `React.addons.classSet` imports.
### 0.4.1 (Apr 30, 2015)
- Remove react/addons dependency (now depending on `react` directly).
- Add MIT License file.
- Fix an issue where browser may be detected as touch-enabled but touch event isn't thrown.
### 0.4.0 (Jan 03, 2015)
- Improving accuracy of snap to grid
- Updating to React 0.12
- Adding dragging className
- Adding reactify support for browserify
- Fixing issue with server side rendering
### 0.3.0 (Oct 21, 2014)
- Adding support for touch devices
### 0.2.1 (Sep 10, 2014)
- Exporting as ReactDraggable
### 0.2.0 (Sep 10, 2014)
- Adding support for snapping to a grid
- Adding support for specifying start position
- Ensure event handlers are destroyed on unmount
- Adding browserify support
- Adding bower support
### 0.1.1 (Jul 26, 2014)
- Fixing dragging not stopping on mouseup in some cases
### 0.1.0 (Jul 25, 2014)
- Initial release

View File

@@ -0,0 +1,21 @@
(MIT License)
Copyright (c) 2014-2016 Matt Zabriskie. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,306 @@
# React-Draggable
[![TravisCI Build Status](https://travis-ci.org/mzabriskie/react-draggable.svg?branch=master)](https://travis-ci.org/mzabriskie/react-draggable)
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/mzabriskie/react-draggable)
[![npm downloads](https://img.shields.io/npm/dt/react-draggable.svg?maxAge=2592000)](http://npmjs.com/package/react-draggable)
[![gzip size](http://img.badgesize.io/https://npmcdn.com/react-draggable/dist/react-draggable.min.js?compression=gzip)]()
[![version](https://img.shields.io/npm/v/react-draggable.svg)]()
A simple component for making elements draggable.
```js
<Draggable>
<div>I can now be moved around!</div>
</Draggable>
```
- [Demo](http://mzabriskie.github.io/react-draggable/example/)
- [Changelog](CHANGELOG.md)
------
#### Technical Documentation
- [Installing](#installing)
- [Exports](#exports)
- [Draggable](#draggable)
- [Draggable Usage](#draggable-usage)
- [Draggable API](#draggable-api)
- [Controlled vs. Uncontrolled](#controlled-vs-uncontrolled)
- [DraggableCore](#draggablecore)
- [DraggableCore API](#draggablecore-api)
### Installing
```bash
$ npm install react-draggable
```
If you aren't using browserify/webpack, a
[UMD version of react-draggable](dist/react-draggable.js) is available. It is updated per-release only.
This bundle is also what is loaded when installing from npm. It expects external `React` and `ReactDOM`.
If you want a UMD version of the latest `master` revision, you can generate it yourself from master by cloning this
repository and running `$ make`. This will create umd dist files in the `dist/` folder.
### Exports
The default export is `<Draggable>`. At the `.DraggableCore` property is [`<DraggableCore>`](#draggablecore).
Here's how to use it:
```js
// ES6
import Draggable from 'react-draggable'; // The default
import {DraggableCore} from 'react-draggable'; // <DraggableCore>
import Draggable, {DraggableCore} from 'react-draggable'; // Both at the same time
// CommonJS
let Draggable = require('react-draggable');
let DraggableCore = Draggable.DraggableCore;
```
## `<Draggable>`
A `<Draggable>` element wraps an existing element and extends it with new event handlers and styles.
It does not create a wrapper element in the DOM.
Draggable items are moved using CSS Transforms. This allows items to be dragged regardless of their current
positioning (relative, absolute, or static). Elements can also be moved between drags without incident.
If the item you are dragging already has a CSS Transform applied, it will be overwritten by `<Draggable>`. Use
an intermediate wrapper (`<Draggable><span>...</span></Draggable>`) in this case.
### Draggable Usage
View the [Demo](http://mzabriskie.github.io/react-draggable/example/) and its
[source](/example/index.html) for more.
```js
import React from 'react';
import ReactDOM from 'react-dom';
import Draggable from 'react-draggable';
class App extends React.Element {
eventLogger = (e: MouseEvent, data: Object) => {
console.log('Event: ', e);
console.log('Data: ', data);
};
render() {
return (
<Draggable
axis="x"
handle=".handle"
defaultPosition={{x: 0, y: 0}}
position={null}
grid={[25, 25]}
onStart={this.handleStart}
onDrag={this.handleDrag}
onStop={this.handleStop}>
<div>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
);
}
}
ReactDOM.render(<App/>, document.body);
```
### Draggable API
The `<Draggable/>` component transparently adds draggability to its children.
**Note**: Only a single child is allowed or an Error will be thrown.
For the `<Draggable/>` component to correctly attach itself to its child, the child element must provide support
for the following props:
- `style` is used to give the transform css to the child.
- `className` is used to apply the proper classes to the object being dragged.
- `onMouseDown`, `onMouseUp`, `onTouchStart`, and `onTouchEnd` are used to keep track of dragging state.
React.DOM elements support the above properties by default, so you may use those elements as children without
any changes. If you wish to use a React component you created, you'll need to be sure to
[transfer prop](https://facebook.github.io/react/docs/transferring-props.html).
#### `<Draggable>` Props:
```js
//
// Types:
//
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
node: HTMLElement,
// lastX + deltaX === x
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
};
//
// Props:
//
{
// If set to `true`, will allow dragging on non left-button clicks.
allowAnyClick: boolean,
// Determines which axis the draggable can move. This only affects
// flushing to the DOM. Callbacks will still include all values.
// Accepted values:
// - `both` allows movement horizontally and vertically (default).
// - `x` limits movement to horizontal axis.
// - `y` limits movement to vertical axis.
// - 'none' stops all movement.
axis: string,
// Specifies movement boundaries. Accepted values:
// - `parent` restricts movement within the node's offsetParent
// (nearest node with position relative or absolute), or
// - a selector, restricts movement within the targeted node
// - An object with `left, top, right, and bottom` properties.
// These indicate how far in each direction the draggable
// can be moved.
bounds: {left: number, top: number, right: number, bottom: number} | string,
// Specifies a selector to be used to prevent drag initialization.
// Example: '.body'
cancel: string,
// Class names for draggable UI.
// Default to 'react-draggable', 'react-draggable-dragging', and 'react-draggable-dragged'
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,
// Specifies the `x` and `y` that the dragged item should start at.
// This is generally not necessary to use (you can use absolute or relative
// positioning of the child directly), but can be helpful for uniformity in
// your callbacks and with css transforms.
defaultPosition: {x: number, y: number},
// If true, will not call any drag handlers.
disabled: boolean,
// Specifies the x and y that dragging should snap to.
grid: [number, number],
// Specifies a selector to be used as the handle that initiates drag.
// Example: '.handle'
handle: string,
// If desired, you can provide your own offsetParent for drag calculations.
// By default, we use the Draggable's offsetParent. This can be useful for elements
// with odd display types or floats.
offsetParent: HTMLElement,
// Called whenever the user mouses down. Called regardless of handle or
// disabled status.
onMouseDown: (e: MouseEvent) => void,
// Called when dragging starts. If `false` is returned any handler,
// the action will cancel.
onStart: DraggableEventHandler,
// Called while dragging.
onDrag: DraggableEventHandler,
// Called when dragging stops.
onStop: DraggableEventHandler,
// Much like React form elements, if this property is present, the item
// becomes 'controlled' and is not responsive to user input. Use `position`
// if you need to have direct control of the element.
position: {x: number, y: number}
}
```
Note that sending `className`, `style`, or `transform` as properties will error - set them on the child element
directly.
## Controlled vs. Uncontrolled
`<Draggable>` is a 'batteries-included' component that manages its own state. If you want to completely
control the lifecycle of the component, use `<DraggableCore>`.
For some users, they may want the nice state management that `<Draggable>` provides, but occasionally want
to programmatically reposition their components. `<Draggable>` allows this customization via a system that
is similar to how React handles form components.
If the prop `position: {x: number, y: number}` is defined, the `<Draggable>` will ignore its internal state and use
the provided position instead. Alternatively, you can seed the position using `defaultPosition`. Technically, since
`<Draggable>` works only on position deltas, you could also seed the initial position using CSS `top/left`.
We make one modification to the React philosophy here - we still allow dragging while a component is controlled.
We then expect you to use at least an `onDrag` or `onStop` handler to synchronize state.
To disable dragging while controlled, send the prop `disabled={true}` - at this point the `<Draggable>` will operate
like a completely static component.
## `<DraggableCore>`
For users that require absolute control, a `<DraggableCore>` element is available. This is useful as an abstraction
over touch and mouse events, but with full control. `<DraggableCore>` has no internal state.
See [React-Resizable](https://github.com/STRML/react-resizable) and
[React-Grid-Layout](https://github.com/STRML/react-grid-layout) for some usage examples.
`<DraggableCore>` is a useful building block for other libraries that simply want to abstract browser-specific
quirks and receive callbacks when a user attempts to move an element. It does not set styles or transforms
on itself and thus must have callbacks attached to be useful.
### DraggableCore API
`<DraggableCore>` takes a limited subset of options:
```js
{
allowAnyClick: boolean,
cancel: string,
disabled: boolean,
enableUserSelectHack: boolean,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
onStart: DraggableEventHandler,
onDrag: DraggableEventHandler,
onStop: DraggableEventHandler,
onMouseDown: (e: MouseEvent) => void
}
```
Note that there is no start position. `<DraggableCore>` simply calls `drag` handlers with the below parameters,
indicating its position (as inferred from the underlying MouseEvent) and deltas. It is up to the parent
to set actual positions on `<DraggableCore>`.
Drag callbacks (`onStart`, `onDrag`, `onStop`) are called with the [same arguments as `<Draggable>`](#draggable-api).
----
### Contributing
- Fork the project
- Run the project in development mode: `$ npm run dev`
- Make changes.
- Add appropriate tests
- `$ npm test`
- If tests don't pass, make them pass.
- Update README with appropriate docs.
- Commit and PR
### Release checklist
- Update CHANGELOG
- `make release-patch`, `make release-minor`, or `make-release-major`
- `make publish`
### License
MIT

View File

@@ -0,0 +1,28 @@
{
"name": "react-draggable",
"version": "3.0.3",
"homepage": "https://github.com/mzabriskie/react-draggable",
"authors": [
"Matt Zabriskie",
"Samuel Reed"
],
"description": "React draggable component",
"main": "./dist/react-draggable.js",
"keywords": [
"react",
"draggable"
],
"license": "MIT",
"ignore": [
"**/.*",
"example",
"lib",
"node_modules",
"script",
"specs",
"index.js",
"karma.conf.js",
"webpack.config.js",
"package.json"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
var Draggable = require('./lib/Draggable').default;
// Previous versions of this lib exported <Draggable> as the root export. As to not break
// them, or TypeScript, we export *both* as the root and as 'default'.
// See https://github.com/mzabriskie/react-draggable/pull/254
// and https://github.com/mzabriskie/react-draggable/issues/266
module.exports = Draggable;
module.exports.default = Draggable;
module.exports.DraggableCore = require('./lib/DraggableCore').default;

View File

@@ -0,0 +1,359 @@
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import {createCSSTransform, createSVGTransform} from './utils/domFns';
import {canDragX, canDragY, createDraggableData, getBoundPosition} from './utils/positionFns';
import {dontSetMe} from './utils/shims';
import DraggableCore from './DraggableCore';
import type {ControlPosition, DraggableBounds, DraggableCoreProps} from './DraggableCore';
import log from './utils/log';
import type {DraggableEventHandler} from './utils/types';
import type {Element as ReactElement} from 'react';
type DraggableState = {
dragging: boolean,
dragged: boolean,
x: number, y: number,
slackX: number, slackY: number,
isElementSVG: boolean
};
export type DraggableProps = {
...$Exact<DraggableCoreProps>,
axis: 'both' | 'x' | 'y' | 'none',
bounds: DraggableBounds | string | false,
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,
defaultPosition: ControlPosition,
position: ControlPosition,
};
//
// Define <Draggable>
//
export default class Draggable extends React.Component<DraggableProps, DraggableState> {
static displayName = 'Draggable';
static propTypes = {
// Accepts all props <DraggableCore> accepts.
...DraggableCore.propTypes,
/**
* `axis` determines which axis the draggable can move.
*
* Note that all callbacks will still return data as normal. This only
* controls flushing to the DOM.
*
* 'both' allows movement horizontally and vertically.
* 'x' limits movement to horizontal axis.
* 'y' limits movement to vertical axis.
* 'none' limits all movement.
*
* Defaults to 'both'.
*/
axis: PropTypes.oneOf(['both', 'x', 'y', 'none']),
/**
* `bounds` determines the range of movement available to the element.
* Available values are:
*
* 'parent' restricts movement within the Draggable's parent node.
*
* Alternatively, pass an object with the following properties, all of which are optional:
*
* {left: LEFT_BOUND, right: RIGHT_BOUND, bottom: BOTTOM_BOUND, top: TOP_BOUND}
*
* All values are in px.
*
* Example:
*
* ```jsx
* let App = React.createClass({
* render: function () {
* return (
* <Draggable bounds={{right: 300, bottom: 300}}>
* <div>Content</div>
* </Draggable>
* );
* }
* });
* ```
*/
bounds: PropTypes.oneOfType([
PropTypes.shape({
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
bottom: PropTypes.number
}),
PropTypes.string,
PropTypes.oneOf([false])
]),
defaultClassName: PropTypes.string,
defaultClassNameDragging: PropTypes.string,
defaultClassNameDragged: PropTypes.string,
/**
* `defaultPosition` specifies the x and y that the dragged item should start at
*
* Example:
*
* ```jsx
* let App = React.createClass({
* render: function () {
* return (
* <Draggable defaultPosition={{x: 25, y: 25}}>
* <div>I start with transformX: 25px and transformY: 25px;</div>
* </Draggable>
* );
* }
* });
* ```
*/
defaultPosition: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
/**
* `position`, if present, defines the current position of the element.
*
* This is similar to how form elements in React work - if no `position` is supplied, the component
* is uncontrolled.
*
* Example:
*
* ```jsx
* let App = React.createClass({
* render: function () {
* return (
* <Draggable position={{x: 25, y: 25}}>
* <div>I start with transformX: 25px and transformY: 25px;</div>
* </Draggable>
* );
* }
* });
* ```
*/
position: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
/**
* These properties should be defined on the child, not here.
*/
className: dontSetMe,
style: dontSetMe,
transform: dontSetMe
};
static defaultProps = {
...DraggableCore.defaultProps,
axis: 'both',
bounds: false,
defaultClassName: 'react-draggable',
defaultClassNameDragging: 'react-draggable-dragging',
defaultClassNameDragged: 'react-draggable-dragged',
defaultPosition: {x: 0, y: 0},
position: null
};
constructor(props: DraggableProps) {
super(props);
this.state = {
// Whether or not we are currently dragging.
dragging: false,
// Whether or not we have been dragged before.
dragged: false,
// Current transform x and y.
x: props.position ? props.position.x : props.defaultPosition.x,
y: props.position ? props.position.y : props.defaultPosition.y,
// Used for compensating for out-of-bounds drags
slackX: 0, slackY: 0,
// Can only determine if SVG after mounting
isElementSVG: false
};
}
componentWillMount() {
if (this.props.position && !(this.props.onDrag || this.props.onStop)) {
// eslint-disable-next-line
console.warn('A `position` was applied to this <Draggable>, without drag handlers. This will make this ' +
'component effectively undraggable. Please attach `onDrag` or `onStop` handlers so you can adjust the ' +
'`position` of this element.');
}
}
componentDidMount() {
// Check to see if the element passed is an instanceof SVGElement
if(typeof window.SVGElement !== 'undefined' && ReactDOM.findDOMNode(this) instanceof window.SVGElement) {
this.setState({ isElementSVG: true });
}
}
componentWillReceiveProps(nextProps: Object) {
// Set x/y if position has changed
if (nextProps.position &&
(!this.props.position ||
nextProps.position.x !== this.props.position.x ||
nextProps.position.y !== this.props.position.y
)
) {
this.setState({ x: nextProps.position.x, y: nextProps.position.y });
}
}
componentWillUnmount() {
this.setState({dragging: false}); // prevents invariant if unmounted while dragging
}
onDragStart: DraggableEventHandler = (e, coreData) => {
log('Draggable: onDragStart: %j', coreData);
// Short-circuit if user's callback killed it.
const shouldStart = this.props.onStart(e, createDraggableData(this, coreData));
// Kills start event on core as well, so move handlers are never bound.
if (shouldStart === false) return false;
this.setState({dragging: true, dragged: true});
};
onDrag: DraggableEventHandler = (e, coreData) => {
if (!this.state.dragging) return false;
log('Draggable: onDrag: %j', coreData);
const uiData = createDraggableData(this, coreData);
const newState: $Shape<DraggableState> = {
x: uiData.x,
y: uiData.y
};
// Keep within bounds.
if (this.props.bounds) {
// Save original x and y.
const {x, y} = newState;
// Add slack to the values used to calculate bound position. This will ensure that if
// we start removing slack, the element won't react to it right away until it's been
// completely removed.
newState.x += this.state.slackX;
newState.y += this.state.slackY;
// Get bound position. This will ceil/floor the x and y within the boundaries.
// $FlowBug
[newState.x, newState.y] = getBoundPosition(this, newState.x, newState.y);
// Recalculate slack by noting how much was shaved by the boundPosition handler.
newState.slackX = this.state.slackX + (x - newState.x);
newState.slackY = this.state.slackY + (y - newState.y);
// Update the event we fire to reflect what really happened after bounds took effect.
uiData.x = newState.x;
uiData.y = newState.y;
uiData.deltaX = newState.x - this.state.x;
uiData.deltaY = newState.y - this.state.y;
}
// Short-circuit if user's callback killed it.
const shouldUpdate = this.props.onDrag(e, uiData);
if (shouldUpdate === false) return false;
this.setState(newState);
};
onDragStop: DraggableEventHandler = (e, coreData) => {
if (!this.state.dragging) return false;
// Short-circuit if user's callback killed it.
const shouldStop = this.props.onStop(e, createDraggableData(this, coreData));
if (shouldStop === false) return false;
log('Draggable: onDragStop: %j', coreData);
const newState: $Shape<DraggableState> = {
dragging: false,
slackX: 0,
slackY: 0
};
// If this is a controlled component, the result of this operation will be to
// revert back to the old position. We expect a handler on `onDragStop`, at the least.
const controlled = Boolean(this.props.position);
if (controlled) {
const {x, y} = this.props.position;
newState.x = x;
newState.y = y;
}
this.setState(newState);
};
render(): ReactElement<any> {
let style = {}, svgTransform = null;
// If this is controlled, we don't want to move it - unless it's dragging.
const controlled = Boolean(this.props.position);
const draggable = !controlled || this.state.dragging;
const position = this.props.position || this.props.defaultPosition;
const transformOpts = {
// Set left if horizontal drag is enabled
x: canDragX(this) && draggable ?
this.state.x :
position.x,
// Set top if vertical drag is enabled
y: canDragY(this) && draggable ?
this.state.y :
position.y
};
// If this element was SVG, we use the `transform` attribute.
if (this.state.isElementSVG) {
svgTransform = createSVGTransform(transformOpts);
} else {
// Add a CSS transform to move the element around. This allows us to move the element around
// without worrying about whether or not it is relatively or absolutely positioned.
// If the item you are dragging already has a transform set, wrap it in a <span> so <Draggable>
// has a clean slate.
style = createCSSTransform(transformOpts);
}
const {
defaultClassName,
defaultClassNameDragging,
defaultClassNameDragged
} = this.props;
// Mark with class while dragging
const className = classNames((this.props.children.props.className || ''), defaultClassName, {
[defaultClassNameDragging]: this.state.dragging,
[defaultClassNameDragged]: this.state.dragged
});
// Reuse the child provided
// This makes it flexible to use whatever element is wanted (div, ul, etc)
return (
<DraggableCore {...this.props} onStart={this.onDragStart} onDrag={this.onDrag} onStop={this.onDragStop}>
{React.cloneElement(React.Children.only(this.props.children), {
className: className,
style: {...this.props.children.props.style, ...style},
transform: svgTransform
})}
</DraggableCore>
);
}
}

View File

@@ -0,0 +1,415 @@
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
removeUserSelectStyles, styleHacks} from './utils/domFns';
import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';
import {dontSetMe} from './utils/shims';
import log from './utils/log';
import type {EventHandler, MouseTouchEvent} from './utils/types';
import type {Element as ReactElement} from 'react';
// Simple abstraction for dragging events names.
const eventsFor = {
touch: {
start: 'touchstart',
move: 'touchmove',
stop: 'touchend'
},
mouse: {
start: 'mousedown',
move: 'mousemove',
stop: 'mouseup'
}
};
// Default to mouse events.
let dragEventFor = eventsFor.mouse;
type DraggableCoreState = {
dragging: boolean,
lastX: number,
lastY: number,
touchIdentifier: ?number
};
export type DraggableBounds = {
left: number,
right: number,
top: number,
bottom: number,
};
export type DraggableData = {
node: HTMLElement,
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number,
};
export type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void;
export type ControlPosition = {x: number, y: number};
export type DraggableCoreProps = {
allowAnyClick: boolean,
cancel: string,
children: ReactElement<any>,
disabled: boolean,
enableUserSelectHack: boolean,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
onStart: DraggableEventHandler,
onDrag: DraggableEventHandler,
onStop: DraggableEventHandler,
onMouseDown: (e: MouseEvent) => void,
};
//
// Define <DraggableCore>.
//
// <DraggableCore> is for advanced usage of <Draggable>. It maintains minimal internal state so it can
// work well with libraries that require more control over the element.
//
export default class DraggableCore extends React.Component<DraggableCoreProps, DraggableCoreState> {
static displayName = 'DraggableCore';
static propTypes = {
/**
* `allowAnyClick` allows dragging using any mouse button.
* By default, we only accept the left button.
*
* Defaults to `false`.
*/
allowAnyClick: PropTypes.bool,
/**
* `disabled`, if true, stops the <Draggable> from dragging. All handlers,
* with the exception of `onMouseDown`, will not fire.
*/
disabled: PropTypes.bool,
/**
* By default, we add 'user-select:none' attributes to the document body
* to prevent ugly text selection during drag. If this is causing problems
* for your app, set this to `false`.
*/
enableUserSelectHack: PropTypes.bool,
/**
* `offsetParent`, if set, uses the passed DOM node to compute drag offsets
* instead of using the parent node.
*/
offsetParent: function(props, propName) {
if (process.browser && props[propName] && props[propName].nodeType !== 1) {
throw new Error('Draggable\'s offsetParent must be a DOM Node.');
}
},
/**
* `grid` specifies the x and y that dragging should snap to.
*/
grid: PropTypes.arrayOf(PropTypes.number),
/**
* `handle` specifies a selector to be used as the handle that initiates drag.
*
* Example:
*
* ```jsx
* let App = React.createClass({
* render: function () {
* return (
* <Draggable handle=".handle">
* <div>
* <div className="handle">Click me to drag</div>
* <div>This is some other content</div>
* </div>
* </Draggable>
* );
* }
* });
* ```
*/
handle: PropTypes.string,
/**
* `cancel` specifies a selector to be used to prevent drag initialization.
*
* Example:
*
* ```jsx
* let App = React.createClass({
* render: function () {
* return(
* <Draggable cancel=".cancel">
* <div>
* <div className="cancel">You can't drag from here</div>
* <div>Dragging here works fine</div>
* </div>
* </Draggable>
* );
* }
* });
* ```
*/
cancel: PropTypes.string,
/**
* Called when dragging starts.
* If this function returns the boolean false, dragging will be canceled.
*/
onStart: PropTypes.func,
/**
* Called while dragging.
* If this function returns the boolean false, dragging will be canceled.
*/
onDrag: PropTypes.func,
/**
* Called when dragging stops.
* If this function returns the boolean false, the drag will remain active.
*/
onStop: PropTypes.func,
/**
* A workaround option which can be passed if onMouseDown needs to be accessed,
* since it'll always be blocked (as there is internal use of onMouseDown)
*/
onMouseDown: PropTypes.func,
/**
* These properties should be defined on the child, not here.
*/
className: dontSetMe,
style: dontSetMe,
transform: dontSetMe
};
static defaultProps = {
allowAnyClick: false, // by default only accept left click
cancel: null,
disabled: false,
enableUserSelectHack: true,
offsetParent: null,
handle: null,
grid: null,
transform: null,
onStart: function(){},
onDrag: function(){},
onStop: function(){},
onMouseDown: function(){}
};
state = {
dragging: false,
// Used while dragging to determine deltas.
lastX: NaN, lastY: NaN,
touchIdentifier: null
};
componentWillUnmount() {
// Remove any leftover event handlers. Remove both touch and mouse handlers in case
// some browser quirk caused a touch event to fire during a mouse move, or vice versa.
const thisNode = ReactDOM.findDOMNode(this);
if (thisNode) {
const {ownerDocument} = thisNode;
removeEvent(ownerDocument, eventsFor.mouse.move, this.handleDrag);
removeEvent(ownerDocument, eventsFor.touch.move, this.handleDrag);
removeEvent(ownerDocument, eventsFor.mouse.stop, this.handleDragStop);
removeEvent(ownerDocument, eventsFor.touch.stop, this.handleDragStop);
if (this.props.enableUserSelectHack) removeUserSelectStyles(ownerDocument);
}
}
handleDragStart: EventHandler<MouseTouchEvent> = (e) => {
// Make it possible to attach event handlers on top of this one.
this.props.onMouseDown(e);
// Only accept left-clicks.
if (!this.props.allowAnyClick && typeof e.button === 'number' && e.button !== 0) return false;
// Get nodes. Be sure to grab relative document (could be iframed)
const thisNode = ReactDOM.findDOMNode(this);
if (!thisNode || !thisNode.ownerDocument || !thisNode.ownerDocument.body) {
throw new Error('<DraggableCore> not mounted on DragStart!');
}
const {ownerDocument} = thisNode;
// Short circuit if handle or cancel prop was provided and selector doesn't match.
if (this.props.disabled ||
(!(e.target instanceof ownerDocument.defaultView.Node)) ||
(this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, thisNode)) ||
(this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, thisNode))) {
return;
}
// Set touch identifier in component state if this is a touch event. This allows us to
// distinguish between individual touches on multitouch screens by identifying which
// touchpoint was set to this element.
const touchIdentifier = getTouchIdentifier(e);
this.setState({touchIdentifier});
// Get the current drag point from the event. This is used as the offset.
const position = getControlPosition(e, touchIdentifier, this);
if (position == null) return; // not possible but satisfies flow
const {x, y} = position;
// Create an event object with all the data parents need to make a decision here.
const coreEvent = createCoreData(this, x, y);
log('DraggableCore: handleDragStart: %j', coreEvent);
// Call event handler. If it returns explicit false, cancel.
log('calling', this.props.onStart);
const shouldUpdate = this.props.onStart(e, coreEvent);
if (shouldUpdate === false) return;
// Add a style to the body to disable user-select. This prevents text from
// being selected all over the page.
if (this.props.enableUserSelectHack) addUserSelectStyles(ownerDocument);
// Initiate dragging. Set the current x and y as offsets
// so we know how much we've moved during the drag. This allows us
// to drag elements around even if they have been moved, without issue.
this.setState({
dragging: true,
lastX: x,
lastY: y
});
// Add events to the document directly so we catch when the user's mouse/touch moves outside of
// this element. We use different events depending on whether or not we have detected that this
// is a touch-capable device.
addEvent(ownerDocument, dragEventFor.move, this.handleDrag);
addEvent(ownerDocument, dragEventFor.stop, this.handleDragStop);
};
handleDrag: EventHandler<MouseTouchEvent> = (e) => {
// Prevent scrolling on mobile devices, like ipad/iphone.
if (e.type === 'touchmove') e.preventDefault();
// Get the current drag point from the event. This is used as the offset.
const position = getControlPosition(e, this.state.touchIdentifier, this);
if (position == null) return;
let {x, y} = position;
// Snap to grid if prop has been provided
if (Array.isArray(this.props.grid)) {
let deltaX = x - this.state.lastX, deltaY = y - this.state.lastY;
[deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
if (!deltaX && !deltaY) return; // skip useless drag
x = this.state.lastX + deltaX, y = this.state.lastY + deltaY;
}
const coreEvent = createCoreData(this, x, y);
log('DraggableCore: handleDrag: %j', coreEvent);
// Call event handler. If it returns explicit false, trigger end.
const shouldUpdate = this.props.onDrag(e, coreEvent);
if (shouldUpdate === false) {
try {
// $FlowIgnore
this.handleDragStop(new MouseEvent('mouseup'));
} catch (err) {
// Old browsers
const event = ((document.createEvent('MouseEvents'): any): MouseTouchEvent);
// I see why this insanity was deprecated
// $FlowIgnore
event.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
this.handleDragStop(event);
}
return;
}
this.setState({
lastX: x,
lastY: y
});
};
handleDragStop: EventHandler<MouseTouchEvent> = (e) => {
if (!this.state.dragging) return;
const position = getControlPosition(e, this.state.touchIdentifier, this);
if (position == null) return;
const {x, y} = position;
const coreEvent = createCoreData(this, x, y);
const thisNode = ReactDOM.findDOMNode(this);
if (thisNode) {
// Remove user-select hack
if (this.props.enableUserSelectHack) removeUserSelectStyles(thisNode.ownerDocument);
}
log('DraggableCore: handleDragStop: %j', coreEvent);
// Reset the el.
this.setState({
dragging: false,
lastX: NaN,
lastY: NaN
});
// Call event handler
this.props.onStop(e, coreEvent);
if (thisNode) {
// Remove event handlers
log('DraggableCore: Removing handlers');
removeEvent(thisNode.ownerDocument, dragEventFor.move, this.handleDrag);
removeEvent(thisNode.ownerDocument, dragEventFor.stop, this.handleDragStop);
}
};
onMouseDown: EventHandler<MouseTouchEvent> = (e) => {
dragEventFor = eventsFor.mouse; // on touchscreen laptops we could switch back to mouse
return this.handleDragStart(e);
};
onMouseUp: EventHandler<MouseTouchEvent> = (e) => {
dragEventFor = eventsFor.mouse;
return this.handleDragStop(e);
};
// Same as onMouseDown (start drag), but now consider this a touch device.
onTouchStart: EventHandler<MouseTouchEvent> = (e) => {
// We're on a touch device now, so change the event handlers
dragEventFor = eventsFor.touch;
return this.handleDragStart(e);
};
onTouchEnd: EventHandler<MouseTouchEvent> = (e) => {
// We're on a touch device now, so change the event handlers
dragEventFor = eventsFor.touch;
return this.handleDragStop(e);
};
render() {
// Reuse the child provided
// This makes it flexible to use whatever element is wanted (div, ul, etc)
return React.cloneElement(React.Children.only(this.props.children), {
style: styleHacks(this.props.children.props.style),
// Note: mouseMove handler is attached to document so it will still function
// when the user drags quickly and leaves the bounds of the element.
onMouseDown: this.onMouseDown,
onTouchStart: this.onTouchStart,
onMouseUp: this.onMouseUp,
onTouchEnd: this.onTouchEnd
});
}
}

View File

@@ -0,0 +1,175 @@
// @flow
import {findInArray, isFunction, int} from './shims';
import browserPrefix, {browserPrefixToKey} from './getPrefix';
import type {ControlPosition, MouseTouchEvent} from './types';
let matchesSelectorFunc = '';
export function matchesSelector(el: Node, selector: string): boolean {
if (!matchesSelectorFunc) {
matchesSelectorFunc = findInArray([
'matches',
'webkitMatchesSelector',
'mozMatchesSelector',
'msMatchesSelector',
'oMatchesSelector'
], function(method){
// $FlowIgnore: Doesn't think elements are indexable
return isFunction(el[method]);
});
}
// $FlowIgnore: Doesn't think elements are indexable
return el[matchesSelectorFunc].call(el, selector);
}
// Works up the tree to the draggable itself attempting to match selector.
export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean {
let node = el;
do {
if (matchesSelector(node, selector)) return true;
if (node === baseNode) return false;
node = node.parentNode;
} while (node);
return false;
}
export function addEvent(el: ?Node, event: string, handler: Function): void {
if (!el) { return; }
if (el.attachEvent) {
el.attachEvent('on' + event, handler);
} else if (el.addEventListener) {
el.addEventListener(event, handler, true);
} else {
// $FlowIgnore: Doesn't think elements are indexable
el['on' + event] = handler;
}
}
export function removeEvent(el: ?Node, event: string, handler: Function): void {
if (!el) { return; }
if (el.detachEvent) {
el.detachEvent('on' + event, handler);
} else if (el.removeEventListener) {
el.removeEventListener(event, handler, true);
} else {
// $FlowIgnore: Doesn't think elements are indexable
el['on' + event] = null;
}
}
export function outerHeight(node: HTMLElement): number {
// This is deliberately excluding margin for our calculations, since we are using
// offsetTop which is including margin. See getBoundPosition
let height = node.clientHeight;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
height += int(computedStyle.borderTopWidth);
height += int(computedStyle.borderBottomWidth);
return height;
}
export function outerWidth(node: HTMLElement): number {
// This is deliberately excluding margin for our calculations, since we are using
// offsetLeft which is including margin. See getBoundPosition
let width = node.clientWidth;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
width += int(computedStyle.borderLeftWidth);
width += int(computedStyle.borderRightWidth);
return width;
}
export function innerHeight(node: HTMLElement): number {
let height = node.clientHeight;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
height -= int(computedStyle.paddingTop);
height -= int(computedStyle.paddingBottom);
return height;
}
export function innerWidth(node: HTMLElement): number {
let width = node.clientWidth;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
width -= int(computedStyle.paddingLeft);
width -= int(computedStyle.paddingRight);
return width;
}
// Get from offsetParent
export function offsetXYFromParent(evt: {clientX: number, clientY: number}, offsetParent: HTMLElement): ControlPosition {
const isBody = offsetParent === offsetParent.ownerDocument.body;
const offsetParentRect = isBody ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();
const x = evt.clientX + offsetParent.scrollLeft - offsetParentRect.left;
const y = evt.clientY + offsetParent.scrollTop - offsetParentRect.top;
return {x, y};
}
export function createCSSTransform({x, y}: {x: number, y: number}): Object {
// Replace unitless items with px
return {[browserPrefixToKey('transform', browserPrefix)]: 'translate(' + x + 'px,' + y + 'px)'};
}
export function createSVGTransform({x, y}: {x: number, y: number}): string {
return 'translate(' + x + ',' + y + ')';
}
export function getTouch(e: MouseTouchEvent, identifier: number): ?{clientX: number, clientY: number} {
return (e.targetTouches && findInArray(e.targetTouches, t => identifier === t.identifier)) ||
(e.changedTouches && findInArray(e.changedTouches, t => identifier === t.identifier));
}
export function getTouchIdentifier(e: MouseTouchEvent): ?number {
if (e.targetTouches && e.targetTouches[0]) return e.targetTouches[0].identifier;
if (e.changedTouches && e.changedTouches[0]) return e.changedTouches[0].identifier;
}
// User-select Hacks:
//
// Useful for preventing blue highlights all over everything when dragging.
// Note we're passing `document` b/c we could be iframed
export function addUserSelectStyles(doc: Document) {
let styleEl = doc.getElementById('react-draggable-style-el');
if (!styleEl) {
styleEl = doc.createElement('style');
styleEl.type = 'text/css';
styleEl.id = 'react-draggable-style-el';
styleEl.innerHTML = '.react-draggable-transparent-selection *::-moz-selection {background: transparent;}\n';
styleEl.innerHTML += '.react-draggable-transparent-selection *::selection {background: transparent;}\n';
doc.getElementsByTagName('head')[0].appendChild(styleEl);
}
if (doc.body) addClassName(doc.body, 'react-draggable-transparent-selection');
}
export function removeUserSelectStyles(doc: Document) {
if (doc.body) removeClassName(doc.body, 'react-draggable-transparent-selection');
window.getSelection().removeAllRanges(); // remove selection caused by scroll
}
export function styleHacks(childStyle: Object = {}): Object {
// Workaround IE pointer events; see #51
// https://github.com/mzabriskie/react-draggable/issues/51#issuecomment-103488278
return {
touchAction: 'none',
...childStyle
};
}
export function addClassName(el: HTMLElement, className: string) {
if (el.classList) {
el.classList.add(className);
} else {
if (!el.className.match(new RegExp(`(?:^|\\s)${className}(?!\\S)`))) {
el.className += ` ${className}`;
}
}
}
export function removeClassName(el: HTMLElement, className: string) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp(`(?:^|\\s)${className}(?!\\S)`, 'g'), '');
}
}

View File

@@ -0,0 +1,47 @@
// @flow
const prefixes = ['Moz', 'Webkit', 'O', 'ms'];
export function getPrefix(prop: string='transform'): string {
// Checking specifically for 'window.document' is for pseudo-browser server-side
// environments that define 'window' as the global context.
// E.g. React-rails (see https://github.com/reactjs/react-rails/pull/84)
if (typeof window === 'undefined' || typeof window.document === 'undefined') return '';
const style = window.document.documentElement.style;
if (prop in style) return '';
for (let i = 0; i < prefixes.length; i++) {
if (browserPrefixToKey(prop, prefixes[i]) in style) return prefixes[i];
}
return '';
}
export function browserPrefixToKey(prop: string, prefix: string): string {
return prefix ? `${prefix}${kebabToTitleCase(prop)}` : prop;
}
export function browserPrefixToStyle(prop: string, prefix: string): string {
return prefix ? `-${prefix.toLowerCase()}-${prop}` : prop;
}
function kebabToTitleCase(str: string): string {
let out = '';
let shouldCapitalize = true;
for (let i = 0; i < str.length; i++) {
if (shouldCapitalize) {
out += str[i].toUpperCase();
shouldCapitalize = false;
} else if (str[i] === '-') {
shouldCapitalize = true;
} else {
out += str[i];
}
}
return out;
}
// Default export is the prefix itself, like 'Moz', 'Webkit', etc
// Note that you may have to re-test for certain things; for instance, Chrome 50
// can handle unprefixed `transform`, but not unprefixed `user-select`
export default getPrefix();

View File

@@ -0,0 +1,5 @@
// @flow
/*eslint no-console:0*/
export default function log(...args: any) {
if (process.env.DRAGGABLE_DEBUG) console.log(...args);
}

View File

@@ -0,0 +1,134 @@
// @flow
import {isNum, int} from './shims';
import ReactDOM from 'react-dom';
import {getTouch, innerWidth, innerHeight, offsetXYFromParent, outerWidth, outerHeight} from './domFns';
import type Draggable from '../Draggable';
import type {Bounds, ControlPosition, DraggableData, MouseTouchEvent} from './types';
import type DraggableCore from '../DraggableCore';
export function getBoundPosition(draggable: Draggable, x: number, y: number): [number, number] {
// If no bounds, short-circuit and move on
if (!draggable.props.bounds) return [x, y];
// Clone new bounds
let {bounds} = draggable.props;
bounds = typeof bounds === 'string' ? bounds : cloneBounds(bounds);
const node = findDOMNode(draggable);
if (typeof bounds === 'string') {
const {ownerDocument} = node;
const ownerWindow = ownerDocument.defaultView;
let boundNode;
if (bounds === 'parent') {
boundNode = node.parentNode;
} else {
boundNode = ownerDocument.querySelector(bounds);
}
if (!(boundNode instanceof HTMLElement)) {
throw new Error('Bounds selector "' + bounds + '" could not find an element.');
}
const nodeStyle = ownerWindow.getComputedStyle(node);
const boundNodeStyle = ownerWindow.getComputedStyle(boundNode);
// Compute bounds. This is a pain with padding and offsets but this gets it exactly right.
bounds = {
left: -node.offsetLeft + int(boundNodeStyle.paddingLeft) + int(nodeStyle.marginLeft),
top: -node.offsetTop + int(boundNodeStyle.paddingTop) + int(nodeStyle.marginTop),
right: innerWidth(boundNode) - outerWidth(node) - node.offsetLeft +
int(boundNodeStyle.paddingRight) - int(nodeStyle.marginRight),
bottom: innerHeight(boundNode) - outerHeight(node) - node.offsetTop +
int(boundNodeStyle.paddingBottom) - int(nodeStyle.marginBottom)
};
}
// Keep x and y below right and bottom limits...
if (isNum(bounds.right)) x = Math.min(x, bounds.right);
if (isNum(bounds.bottom)) y = Math.min(y, bounds.bottom);
// But above left and top limits.
if (isNum(bounds.left)) x = Math.max(x, bounds.left);
if (isNum(bounds.top)) y = Math.max(y, bounds.top);
return [x, y];
}
export function snapToGrid(grid: [number, number], pendingX: number, pendingY: number): [number, number] {
const x = Math.round(pendingX / grid[0]) * grid[0];
const y = Math.round(pendingY / grid[1]) * grid[1];
return [x, y];
}
export function canDragX(draggable: Draggable): boolean {
return draggable.props.axis === 'both' || draggable.props.axis === 'x';
}
export function canDragY(draggable: Draggable): boolean {
return draggable.props.axis === 'both' || draggable.props.axis === 'y';
}
// Get {x, y} positions from event.
export function getControlPosition(e: MouseTouchEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition {
const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null;
if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch
const node = findDOMNode(draggableCore);
// User can provide an offsetParent if desired.
const offsetParent = draggableCore.props.offsetParent || node.offsetParent || node.ownerDocument.body;
return offsetXYFromParent(touchObj || e, offsetParent);
}
// Create an data object exposed by <DraggableCore>'s events
export function createCoreData(draggable: DraggableCore, x: number, y: number): DraggableData {
const state = draggable.state;
const isStart = !isNum(state.lastX);
const node = findDOMNode(draggable);
if (isStart) {
// If this is our first move, use the x and y as last coords.
return {
node,
deltaX: 0, deltaY: 0,
lastX: x, lastY: y,
x, y,
};
} else {
// Otherwise calculate proper values.
return {
node,
deltaX: x - state.lastX, deltaY: y - state.lastY,
lastX: state.lastX, lastY: state.lastY,
x, y,
};
}
}
// Create an data exposed by <Draggable>'s events
export function createDraggableData(draggable: Draggable, coreData: DraggableData): DraggableData {
return {
node: coreData.node,
x: draggable.state.x + coreData.deltaX,
y: draggable.state.y + coreData.deltaY,
deltaX: coreData.deltaX,
deltaY: coreData.deltaY,
lastX: draggable.state.x,
lastY: draggable.state.y
};
}
// A lot faster than stringify/parse
function cloneBounds(bounds: Bounds): Bounds {
return {
left: bounds.left,
top: bounds.top,
right: bounds.right,
bottom: bounds.bottom
};
}
function findDOMNode(draggable: Draggable | DraggableCore): HTMLElement {
const node = ReactDOM.findDOMNode(draggable);
if (!node) {
throw new Error('<DraggableCore>: Unmounted during event!');
}
// $FlowIgnore we can't assert on HTMLElement due to tests... FIXME
return node;
}

View File

@@ -0,0 +1,25 @@
// @flow
// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc
export function findInArray(array: Array<any> | TouchList, callback: Function): any {
for (let i = 0, length = array.length; i < length; i++) {
if (callback.apply(callback, [array[i], i, array])) return array[i];
}
}
export function isFunction(func: any): boolean {
return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]';
}
export function isNum(num: any): boolean {
return typeof num === 'number' && !isNaN(num);
}
export function int(a: string): number {
return parseInt(a, 10);
}
export function dontSetMe(props: Object, propName: string, componentName: string) {
if (props[propName]) {
return new Error(`Invalid prop ${propName} passed to ${componentName} - do not set this, set it on the child.`);
}
}

View File

@@ -0,0 +1,29 @@
// @flow
// eslint-disable-next-line no-use-before-define
export type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void | false;
export type DraggableData = {
node: HTMLElement,
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
};
export type Bounds = {
left: number, top: number, right: number, bottom: number
};
export type ControlPosition = {x: number, y: number};
export type EventHandler<T> = (e: T) => void | false;
// Missing in Flow
export class SVGElement extends HTMLElement {
}
// Missing targetTouches
export class TouchEvent2 extends TouchEvent {
changedTouches: TouchList;
targetTouches: TouchList;
}
export type MouseTouchEvent = MouseEvent & TouchEvent2;

View File

@@ -0,0 +1,112 @@
{
"_args": [
[
"react-draggable@3.0.3",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "react-draggable@3.0.3",
"_id": "react-draggable@3.0.3",
"_inBundle": false,
"_integrity": "sha512-ekb3dKvZFC6QaBNBpAWwGk9aoaDPkSVkrZl/LEU92THoX72Pv6uQ2ZpfYjmBrbrTQcAYmuUAkp9pHJ7j8FUYJA==",
"_location": "/react-grid-layout/react-draggable",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "react-draggable@3.0.3",
"name": "react-draggable",
"escapedName": "react-draggable",
"rawSpec": "3.0.3",
"saveSpec": null,
"fetchSpec": "3.0.3"
},
"_requiredBy": [
"/react-grid-layout",
"/react-grid-layout/react-resizable"
],
"_resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-3.0.3.tgz",
"_spec": "3.0.3",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "Matt Zabriskie"
},
"browser": "dist/react-draggable.js",
"bugs": {
"url": "https://github.com/mzabriskie/react-draggable/issues"
},
"dependencies": {
"classnames": "^2.2.5",
"prop-types": "^15.5.10"
},
"description": "React draggable component",
"devDependencies": {
"@types/react": "^16.0.0",
"@types/react-dom": "^15.5.3",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-espower": "^2.3.2",
"babel-plugin-transform-flow-comments": "^6.22.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"eslint": "^4.5.0",
"eslint-plugin-react": "^7.2.1",
"flow-bin": "^0.53.1",
"jasmine-core": "^2.7.0",
"json-loader": "^0.5.7",
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "1.0.1",
"karma-firefox-launcher": "^1.0.1",
"karma-ie-launcher": "^1.0.0",
"karma-jasmine": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-phantomjs-shim": "^1.4.0",
"karma-webpack": "^2.0.4",
"lodash": "^4.17.4",
"open": "0.0.5",
"phantomjs-prebuilt": "^2.1.15",
"power-assert": "^1.4.4",
"pre-commit": "^1.2.2",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-frame-component": "^1.1.1",
"semver": "^5.4.1",
"static-server": "^2.0.5",
"typescript": "^2.4.2",
"uglify-js": "^3.0.28",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1"
},
"homepage": "https://github.com/mzabriskie/react-draggable",
"keywords": [
"react",
"draggable",
"react-component"
],
"license": "MIT",
"main": "dist/react-draggable.js",
"name": "react-draggable",
"precommit": [
"lint",
"test"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mzabriskie/react-draggable.git"
},
"scripts": {
"build": "make clean build",
"dev": "make dev",
"flow": "flow",
"lint": "make lint",
"test": "make test",
"test-debug": "karma start --browsers=Chrome",
"test-ie": "karma start --browsers=IE"
},
"typings": "./typings/index.d.ts",
"version": "3.0.3"
}

View File

@@ -0,0 +1,53 @@
declare module 'react-draggable' {
import * as React from 'react';
export interface DraggableBounds {
left: number
right: number
top: number
bottom: number
}
export interface DraggableProps extends DraggableCoreProps {
axis: 'both' | 'x' | 'y' | 'none',
bounds: DraggableBounds | string | false ,
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,
defaultPosition: ControlPosition,
position: ControlPosition
}
export type DraggableEventHandler = (e: MouseEvent, data: DraggableData) => void | false;
export interface DraggableData {
node: HTMLElement,
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
}
export type ControlPosition = {x: number, y: number};
export interface DraggableCoreProps {
allowAnyClick: boolean,
cancel: string,
disabled: boolean,
enableUserSelectHack: boolean,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
onStart: DraggableEventHandler,
onDrag: DraggableEventHandler,
onStop: DraggableEventHandler,
onMouseDown: (e: MouseEvent) => void
}
export default class Draggable extends React.Component<Partial<DraggableProps>, {}> {
static defaultProps : DraggableProps;
}
export class DraggableCore extends React.Component<Partial<DraggableCoreProps>, {}> {
static defaultProps : DraggableCoreProps;
}
}

View File

@@ -0,0 +1,64 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Draggable, {DraggableCore} from 'react-draggable';
const root = document.getElementById('root')
function handleStart() {}
function handleDrag() {}
function handleStop() {}
function handleMouseDown() {}
ReactDOM.render(
<Draggable
axis="y"
handle=".handle"
cancel=".cancel"
grid={[10, 10]}
onStart={handleStart}
onDrag={handleDrag}
onStop={handleStop}
offsetParent={document.body}
allowAnyClick={true}
onMouseDown={handleMouseDown}
disabled={true}
enableUserSelectHack={false}
bounds={false}
defaultClassName={'draggable'}
defaultClassNameDragging={'dragging'}
defaultClassNameDragged={'dragged'}
defaultPosition={{x: 0, y: 0}}
position={{x: 50, y: 50}}>
<div className="foo bar">
<div className="handle"/>
<div className="cancel"/>
</div>
</Draggable>,
root
);
ReactDOM.render(
<DraggableCore
handle=".handle"
cancel=".cancel"
allowAnyClick={true}
disabled={true}
onMouseDown={handleMouseDown}
grid={[10, 10]}
onStart={handleStart}
onDrag={handleDrag}
onStop={handleStop}
offsetParent={document.body}
enableUserSelectHack={false}>
<div className="foo bar">
<div className="handle"/>
<div className="cancel"/>
</div>
</DraggableCore>,
root
);
ReactDOM.render(<Draggable><div/></Draggable>, root);
ReactDOM.render(<DraggableCore><div/></DraggableCore>, root);

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"noEmit": true,
"jsx": "preserve",
"strict": true
},
"files": [
"index.d.ts",
"test.tsx"
]
}

View File

@@ -0,0 +1,48 @@
var webpack = require('webpack');
module.exports = {
entry: './index.js',
output: {
filename: './dist/react-draggable.js',
sourceMapFilename: './dist/react-draggable.js.map',
devtoolModuleFilenameTemplate: '../[resource-path]',
library: 'ReactDraggable',
libraryTarget: 'umd'
},
externals: {
'react': {
'commonjs': 'react',
'commonjs2': 'react',
'amd': 'react',
// React dep should be available as window.React, not window.react
'root': 'React'
},
'react-dom': {
'commonjs': 'react-dom',
'commonjs2': 'react-dom',
'amd': 'react-dom',
'root': 'ReactDOM'
}
},
module: {
rules: [
{
test: /\.(?:js|es).?$/,
loader: 'babel-loader?cacheDirectory',
exclude: /(node_modules)/
}
]
},
resolve: {
extensions: ['.js']
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
DRAGGABLE_DEBUG: process.env.DRAGGABLE_DEBUG
}
}),
// Scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
]
};