Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for using React Ref to specify handle and cancel #680

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
@@ -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'
@@ -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
43 changes: 33 additions & 10 deletions lib/DraggableCore.js
Original file line number Diff line number Diff line change
@@ -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';
@@ -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,
@@ -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>,
};

@@ -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:
*
@@ -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:
*
@@ -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
@@ -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;
}

10 changes: 7 additions & 3 deletions lib/utils/domFns.js
Original file line number Diff line number Diff line change
@@ -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);
26 changes: 26 additions & 0 deletions specs/draggable.spec.jsx
Original file line number Diff line number Diff line change
@@ -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 () {
@@ -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 () {
4 changes: 2 additions & 2 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -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,