diff --git a/lib/Draggable.js b/lib/Draggable.js index 7e53ecfe..163592ec 100644 --- a/lib/Draggable.js +++ b/lib/Draggable.js @@ -31,7 +31,8 @@ export type DraggableProps = { defaultPosition: ControlPosition, positionOffset: PositionOffsetControlPosition, position: ControlPosition, - scale: number + scale: number, + rotate: number }; // @@ -350,7 +351,9 @@ class Draggable extends React.Component { // Set top if vertical drag is enabled y: canDragY(this) && draggable ? this.state.y : - validPosition.y + validPosition.y, + + r: this.props.rotate ? this.props.rotate : 0 }; // If this element was SVG, we use the `transform` attribute. diff --git a/lib/utils/domFns.js b/lib/utils/domFns.js index 6963da72..6e12ad5c 100644 --- a/lib/utils/domFns.js +++ b/lib/utils/domFns.js @@ -1,22 +1,29 @@ // @flow -import {findInArray, isFunction, int} from './shims'; -import browserPrefix, {browserPrefixToKey} from './getPrefix'; +import { findInArray, isFunction, int } from "./shims"; +import browserPrefix, { browserPrefixToKey } from "./getPrefix"; -import type {ControlPosition, PositionOffsetControlPosition, MouseTouchEvent} from './types'; +import type { + ControlPosition, + PositionOffsetControlPosition, + MouseTouchEvent +} from "./types"; -let matchesSelectorFunc = ''; +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]); - }); + matchesSelectorFunc = findInArray( + [ + "matches", + "webkitMatchesSelector", + "mozMatchesSelector", + "msMatchesSelector", + "oMatchesSelector" + ], + function(method) { + // $FlowIgnore: Doesn't think elements are indexable + return isFunction(el[method]); + } + ); } // Might not be found entirely (not an Element?) - in that case, bail @@ -28,7 +35,11 @@ export function matchesSelector(el: Node, selector: string): boolean { } // Works up the tree to the draggable itself attempting to match selector. -export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean { +export function matchesSelectorAndParentsTo( + el: Node, + selector: string, + baseNode: Node +): boolean { let node = el; do { if (matchesSelector(node, selector)) return true; @@ -40,26 +51,30 @@ export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode } export function addEvent(el: ?Node, event: string, handler: Function): void { - if (!el) { return; } + if (!el) { + return; + } if (el.attachEvent) { - el.attachEvent('on' + event, handler); + 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; + el["on" + event] = handler; } } export function removeEvent(el: ?Node, event: string, handler: Function): void { - if (!el) { return; } + if (!el) { + return; + } if (el.detachEvent) { - el.detachEvent('on' + event, handler); + 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; + el["on" + event] = null; } } @@ -99,43 +114,78 @@ export function innerWidth(node: HTMLElement): number { } // Get from offsetParent -export function offsetXYFromParent(evt: {clientX: number, clientY: number}, offsetParent: HTMLElement, scale: number): ControlPosition { +export function offsetXYFromParent( + evt: { clientX: number, clientY: number }, + offsetParent: HTMLElement, + scale: number +): ControlPosition { const isBody = offsetParent === offsetParent.ownerDocument.body; - const offsetParentRect = isBody ? {left: 0, top: 0} : offsetParent.getBoundingClientRect(); + const offsetParentRect = isBody + ? { left: 0, top: 0 } + : offsetParent.getBoundingClientRect(); - const x = (evt.clientX + offsetParent.scrollLeft - offsetParentRect.left) / scale; - const y = (evt.clientY + offsetParent.scrollTop - offsetParentRect.top) / scale; + const x = + (evt.clientX + offsetParent.scrollLeft - offsetParentRect.left) / scale; + const y = + (evt.clientY + offsetParent.scrollTop - offsetParentRect.top) / scale; - return {x, y}; + return { x, y, r: 0 }; } -export function createCSSTransform(controlPos: ControlPosition, positionOffset: PositionOffsetControlPosition): Object { - const translation = getTranslation(controlPos, positionOffset, 'px'); - return {[browserPrefixToKey('transform', browserPrefix)]: translation }; +export function createCSSTransform( + controlPos: ControlPosition, + positionOffset: PositionOffsetControlPosition +): Object { + const translation = getTranslation(controlPos, positionOffset, "px"); + return { [browserPrefixToKey("transform", browserPrefix)]: translation }; } -export function createSVGTransform(controlPos: ControlPosition, positionOffset: PositionOffsetControlPosition): string { - const translation = getTranslation(controlPos, positionOffset, ''); +export function createSVGTransform( + controlPos: ControlPosition, + positionOffset: PositionOffsetControlPosition +): string { + const translation = getTranslation(controlPos, positionOffset, ""); return translation; } -export function getTranslation({x, y}: ControlPosition, positionOffset: PositionOffsetControlPosition, unitSuffix: string): string { - let translation = `translate(${x}${unitSuffix},${y}${unitSuffix})`; +export function getTranslation( + { x, y, r }: ControlPosition, + positionOffset: PositionOffsetControlPosition, + unitSuffix: string +): string { + let translation = `translate(${x}${unitSuffix},${y}${unitSuffix}) rotate(${r}deg)`; if (positionOffset) { - const defaultX = `${(typeof positionOffset.x === 'string') ? positionOffset.x : positionOffset.x + unitSuffix}`; - const defaultY = `${(typeof positionOffset.y === 'string') ? positionOffset.y : positionOffset.y + unitSuffix}`; + const defaultX = `${ + typeof positionOffset.x === "string" + ? positionOffset.x + : positionOffset.x + unitSuffix + }`; + const defaultY = `${ + typeof positionOffset.y === "string" + ? positionOffset.y + : positionOffset.y + unitSuffix + }`; translation = `translate(${defaultX}, ${defaultY})` + translation; } return translation; } -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 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; + 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: @@ -145,27 +195,30 @@ export function getTouchIdentifier(e: MouseTouchEvent): ?number { // Note we're passing `document` b/c we could be iframed export function addUserSelectStyles(doc: ?Document) { if (!doc) return; - let styleEl = doc.getElementById('react-draggable-style-el'); + 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 {all: inherit;}\n'; - styleEl.innerHTML += '.react-draggable-transparent-selection *::selection {all: inherit;}\n'; - doc.getElementsByTagName('head')[0].appendChild(styleEl); + styleEl = doc.createElement("style"); + styleEl.type = "text/css"; + styleEl.id = "react-draggable-style-el"; + styleEl.innerHTML = + ".react-draggable-transparent-selection *::-moz-selection {all: inherit;}\n"; + styleEl.innerHTML += + ".react-draggable-transparent-selection *::selection {all: inherit;}\n"; + doc.getElementsByTagName("head")[0].appendChild(styleEl); } - if (doc.body) addClassName(doc.body, 'react-draggable-transparent-selection'); + if (doc.body) addClassName(doc.body, "react-draggable-transparent-selection"); } export function removeUserSelectStyles(doc: ?Document) { try { - if (doc && doc.body) removeClassName(doc.body, 'react-draggable-transparent-selection'); + if (doc && doc.body) + removeClassName(doc.body, "react-draggable-transparent-selection"); // $FlowIgnore: IE if (doc.selection) { // $FlowIgnore: IE doc.selection.empty(); } else { - window.getSelection().removeAllRanges(); // remove selection caused by scroll + window.getSelection().removeAllRanges(); // remove selection caused by scroll } } catch (e) { // probably IE @@ -176,7 +229,7 @@ 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', + touchAction: "none", ...childStyle }; } @@ -195,6 +248,9 @@ 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'), ''); + el.className = el.className.replace( + new RegExp(`(?:^|\\s)${className}(?!\\S)`, "g"), + "" + ); } } diff --git a/lib/utils/types.js b/lib/utils/types.js index 5321f63c..101c975e 100644 --- a/lib/utils/types.js +++ b/lib/utils/types.js @@ -13,7 +13,7 @@ export type DraggableData = { export type Bounds = { left: number, top: number, right: number, bottom: number }; -export type ControlPosition = {x: number, y: number}; +export type ControlPosition = {x: number, y: number, r: number}; export type PositionOffsetControlPosition = {x: number|string, y: number|string}; export type EventHandler = (e: T) => void | false; diff --git a/package.json b/package.json index bc89f1b0..52c9c380 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-draggable", - "version": "4.2.0", + "name": "react-draggable-rotatable", + "version": "4.2.1", "description": "React draggable component", "main": "build/cjs/cjs.js", "browser": "build/web/react-draggable.min.js", @@ -91,4 +91,4 @@ "classnames": "^2.2.5", "prop-types": "^15.6.0" } -} \ No newline at end of file +} diff --git a/specs/draggable.spec.jsx b/specs/draggable.spec.jsx index 376f2afe..e2a07055 100644 --- a/specs/draggable.spec.jsx +++ b/specs/draggable.spec.jsx @@ -254,7 +254,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(style.indexOf('transform: translate(100px, 100px);') >= 0); + assert(style.indexOf('transform: translate(100px, 100px) rotate(0deg);') >= 0); }); it('should render with positionOffset set as string transform and handle subsequent translate() for DOM nodes', function () { @@ -270,7 +270,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(style.indexOf('translate(10%, 10%) translate(100px, 100px);') >= 0); + assert(style.indexOf('translate(10%, 10%) translate(100px, 100px) rotate(0deg);') >= 0); }); it('should honor "x" axis', function () { @@ -286,7 +286,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(/transform: translate\(100px(?:, 0px)?\);/.test(style)); + assert(/transform: translate\(100px(?:, 0px)?\) rotate\(0deg\);/.test(style)); }); it('should honor "y" axis', function () { @@ -302,7 +302,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(style.indexOf('transform: translate(0px, 100px);') >= 0); + assert(style.indexOf('transform: translate(0px, 100px) rotate(0deg);') >= 0); }); it('should honor "none" axis', function () { @@ -318,7 +318,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(/transform: translate\(0px(?:, 0px)?\);/.test(style)); + assert(/transform: translate\(0px(?:, 0px)?\) rotate\(0deg\);/.test(style)); }); it('should detect if an element is instanceof SVGElement and set state.isElementSVG to true', function() { @@ -426,7 +426,7 @@ describe('react-draggable', function () { const style = node.getAttribute('style'); assert(dragged === true); - assert(style.indexOf('transform: translate(100px, 100px);') >= 0); + assert(style.indexOf('transform: translate(100px, 100px) rotate(0deg);') >= 0); renderRoot.parentNode.removeChild(renderRoot); done(); diff --git a/typings/index.d.ts b/typings/index.d.ts index cee91a44..fb59b693 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,28 +1,29 @@ -declare module 'react-draggable' { - import * as React from 'react'; +declare module "react-draggable" { + import * as React from "react"; export interface DraggableBounds { - left: number - right: number - top: number - bottom: number + 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, - positionOffset: PositionOffsetControlPosition, - position: ControlPosition + axis: "both" | "x" | "y" | "none"; + bounds: DraggableBounds | string | false; + defaultClassName: string; + defaultClassNameDragging: string; + defaultClassNameDragged: string; + defaultPosition: ControlPosition; + positionOffset: PositionOffsetControlPosition; + position: ControlPosition; } - export type DraggableEvent = React.MouseEvent + export type DraggableEvent = + | React.MouseEvent | React.TouchEvent | MouseEvent - | TouchEvent + | TouchEvent; export type DraggableEventHandler = ( e: DraggableEvent, @@ -30,36 +31,48 @@ declare module 'react-draggable' { ) => void | false; export interface DraggableData { - node: HTMLElement, - x: number, y: number, - deltaX: number, deltaY: number, - lastX: number, lastY: number + node: HTMLElement; + x: number; + y: number; + deltaX: number; + deltaY: number; + lastX: number; + lastY: number; } - export type ControlPosition = {x: number, y: number}; + export type ControlPosition = { x: number; y: number; r: number }; - export type PositionOffsetControlPosition = {x: number|string, y: number|string}; + export type PositionOffsetControlPosition = { + x: number | string; + y: number | string; + }; 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, - scale: number + 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; + scale: number; } - export default class Draggable extends React.Component, {}> { - static defaultProps : DraggableProps; + export default class Draggable extends React.Component< + Partial, + {} + > { + static defaultProps: DraggableProps; } - export class DraggableCore extends React.Component, {}> { - static defaultProps : DraggableCoreProps; + export class DraggableCore extends React.Component< + Partial, + {} + > { + static defaultProps: DraggableCoreProps; } } diff --git a/typings/test.tsx b/typings/test.tsx index 17377dcc..607835d2 100644 --- a/typings/test.tsx +++ b/typings/test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import Draggable, {DraggableCore} from 'react-draggable'; +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import Draggable, { DraggableCore } from "react-draggable-rotatable"; -const root = document.getElementById('root') +const root = document.getElementById("root"); function handleStart() {} function handleDrag() {} @@ -24,15 +24,16 @@ ReactDOM.render( disabled={true} enableUserSelectHack={false} bounds={false} - defaultClassName={'draggable'} - defaultClassNameDragging={'dragging'} - defaultClassNameDragged={'dragged'} - defaultPosition={{x: 0, y: 0}} - positionOffset={{x: 0, y: 0}} - position={{x: 50, y: 50}}> + defaultClassName={"draggable"} + defaultClassNameDragging={"dragging"} + defaultClassNameDragged={"dragged"} + defaultPosition={{ x: 0, y: 0, r: 0 }} + positionOffset={{ x: 0, y: 0 }} + position={{ x: 50, y: 50, r: 0 }} + >
-
-
+
+
, root @@ -50,16 +51,26 @@ ReactDOM.render( onDrag={handleDrag} onStop={handleStop} offsetParent={document.body} - enableUserSelectHack={false}> + enableUserSelectHack={false} + >
-
-
+
+
, root ); +ReactDOM.render( + +
+ , + root +); -ReactDOM.render(
, root); - -ReactDOM.render(
, root); +ReactDOM.render( + +
+ , + root +);