Skip to content
Open
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ axis: string,
// can be moved.
bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,

// Specifies a selector to be used to prevent drag initialization. The string is passed to
// Specifies a ref or selector to be used to prevent drag initialization. The selector is passed to
// Element.matches, so it's possible to use multiple selectors like `.first, .second`.
// Example: '.body'
cancel: string,
cancel: string | React.Ref<typeof React.Component>,

// Class names for draggable UI.
// Default to 'react-draggable', 'react-draggable-dragging', and 'react-draggable-dragged'
Expand All @@ -205,9 +205,9 @@ 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.
// Specifies a ref or selector to be used as the handle that initiates drag.
// Example: '.handle'
handle: string,
handle: string | React.Ref<typeof React.Component>,

// 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
Expand Down
43 changes: 33 additions & 10 deletions lib/DraggableCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
removeUserSelectStyles} from './utils/domFns';
import {
matchesNodeOrSelectorAndParentsTo,
addEvent,
removeEvent,
addUserSelectStyles,
getTouchIdentifier,
removeUserSelectStyles,
} from './utils/domFns';
import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';
import {dontSetMe} from './utils/shims';
import log from './utils/log';
Expand Down Expand Up @@ -35,6 +41,8 @@ type DraggableCoreState = {
touchIdentifier: ?number
};

type ReactRefOrSelector<T: HTMLElement> = { current: null | T } | string;

export type DraggableData = {
node: HTMLElement,
x: number, y: number,
Expand All @@ -60,11 +68,11 @@ export type DraggableCoreDefaultProps = {

export type DraggableCoreProps = {
...DraggableCoreDefaultProps,
cancel: string,
cancel: ReactRefOrSelector<HTMLElement>,
children: ReactElement<any>,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
handle: ReactRefOrSelector<HTMLElement>,
nodeRef?: ?React.ElementRef<any>,
};

Expand Down Expand Up @@ -117,7 +125,7 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
grid: PropTypes.arrayOf(PropTypes.number),

/**
* `handle` specifies a selector to be used as the handle that initiates drag.
* `handle` specifies a ref or selector to be used as the handle that initiates drag.
*
* Example:
*
Expand All @@ -136,10 +144,13 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
* });
* ```
*/
handle: PropTypes.string,
handle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),

/**
* `cancel` specifies a selector to be used to prevent drag initialization.
* `cancel` specifies a ref or selector to be used to prevent drag initialization.
*
* Example:
*
Expand All @@ -158,7 +169,10 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
* });
* ```
*/
cancel: PropTypes.string,
cancel: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),

/* If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
* Unfortunately, in order for <Draggable> to work properly, we need raw access
Expand Down Expand Up @@ -282,11 +296,20 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
}
const {ownerDocument} = thisNode;

function getNodeOrSelector(refOrSelector: ReactRefOrSelector<HTMLElement>): HTMLElement | string | null {
return refOrSelector
? typeof refOrSelector === 'string'
? refOrSelector
: refOrSelector.current
: null;
}
const handle = getNodeOrSelector(this.props.handle);
const cancel = getNodeOrSelector(this.props.cancel);
// 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))) {
(handle && !matchesNodeOrSelectorAndParentsTo(e.target, handle, thisNode)) ||
(cancel && matchesNodeOrSelectorAndParentsTo(e.target, cancel, thisNode))) {
return;
}

Expand Down
10 changes: 7 additions & 3 deletions lib/utils/domFns.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ export function matchesSelector(el: Node, selector: string): boolean {
return el[matchesSelectorFunc](selector);
}

// Works up the tree to the draggable itself attempting to match selector.
export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean {
// Works up the tree to the draggable itself attempting to match node or selector.
export function matchesNodeOrSelectorAndParentsTo(el: Node, nodeOrSelector: string | Node, baseNode: Node): boolean {
let node = el;
do {
if (matchesSelector(node, selector)) return true;
if (
typeof nodeOrSelector === 'string'
? matchesSelector(node, nodeOrSelector)
: node === nodeOrSelector
) return true;
if (node === baseNode) return false;
node = node.parentNode;
} while (node);
Expand Down
26 changes: 26 additions & 0 deletions specs/draggable.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,19 @@ describe('react-draggable', function () {

mouseDownOn(drag, '.content', false);
mouseDownOn(drag, '.handle', true);

const handle = React.createRef();
drag = TestUtils.renderIntoDocument(
<Draggable handle={handle}>
<div>
<div ref={handle} className="handle">Handle</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);

mouseDownOn(drag, '.content', false);
mouseDownOn(drag, '.handle', true);
});

it('should only initialize dragging onmousedown of handle, even if children fire event', function () {
Expand Down Expand Up @@ -570,6 +583,19 @@ describe('react-draggable', function () {

mouseDownOn(drag, '.cancel', false);
mouseDownOn(drag, '.content', true);

const cancel = React.createRef();
drag = TestUtils.renderIntoDocument(
<Draggable cancel={cancel}>
<div>
<div ref={cancel} className="cancel">Cancel</div>
<div className="content">Lorem ipsum...</div>
</div>
</Draggable>
);

mouseDownOn(drag, '.cancel', false);
mouseDownOn(drag, '.content', true);
});

it('should not initialize dragging onmousedown of handle, even if children fire event', function () {
Expand Down
4 changes: 2 additions & 2 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ declare module 'react-draggable' {

export interface DraggableCoreProps {
allowAnyClick: boolean,
cancel: string,
cancel: string | React.RefObject<HTMLElement>,
children?: React.ReactNode,
disabled: boolean,
enableUserSelectHack: boolean,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
handle: string | React.RefObject<HTMLElement>,
nodeRef?: React.RefObject<HTMLElement>,
onStart: DraggableEventHandler,
onDrag: DraggableEventHandler,
Expand Down