Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/Draggable.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export type DraggableProps = {
defaultPosition: ControlPosition,
positionOffset: PositionOffsetControlPosition,
position: ControlPosition,
scale: number
scale: number,
rotate: number
};

//
Expand Down Expand Up @@ -350,7 +351,9 @@ class Draggable extends React.Component<DraggableProps, DraggableState> {
// 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.
Expand Down
160 changes: 108 additions & 52 deletions lib/utils/domFns.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
};
}
Expand All @@ -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"),
""
);
}
}
2 changes: 1 addition & 1 deletion lib/utils/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = (e: T) => void | false;

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -91,4 +91,4 @@
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
}
}
}
12 changes: 6 additions & 6 deletions specs/draggable.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -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 () {
Expand All @@ -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 () {
Expand All @@ -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 () {
Expand All @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down
Loading