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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
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
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
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
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
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