diff --git a/README.md b/README.md index 50cfefd6..418ac32e 100644 --- a/README.md +++ b/README.md @@ -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, // 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, // 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 diff --git a/lib/DraggableCore.js b/lib/DraggableCore.js index 4827f36c..a827b965 100644 --- a/lib/DraggableCore.js +++ b/lib/DraggableCore.js @@ -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 = { 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, children: ReactElement, offsetParent: HTMLElement, grid: [number, number], - handle: string, + handle: ReactRefOrSelector, nodeRef?: ?React.ElementRef, }; @@ -117,7 +125,7 @@ export default class DraggableCore extends React.Component to work properly, we need raw access @@ -282,11 +296,20 @@ export default class DraggableCore extends React.Component): 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; } diff --git a/lib/utils/domFns.js b/lib/utils/domFns.js index 65fe32fe..d0bdb5b1 100644 --- a/lib/utils/domFns.js +++ b/lib/utils/domFns.js @@ -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); diff --git a/specs/draggable.spec.jsx b/specs/draggable.spec.jsx index 51ed3b4d..4abb6bf1 100644 --- a/specs/draggable.spec.jsx +++ b/specs/draggable.spec.jsx @@ -537,6 +537,19 @@ describe('react-draggable', function () { mouseDownOn(drag, '.content', false); mouseDownOn(drag, '.handle', true); + + const handle = React.createRef(); + drag = TestUtils.renderIntoDocument( + +
+
Handle
+
Lorem ipsum...
+
+
+ ); + + 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( + +
+
Cancel
+
Lorem ipsum...
+
+
+ ); + + mouseDownOn(drag, '.cancel', false); + mouseDownOn(drag, '.content', true); }); it('should not initialize dragging onmousedown of handle, even if children fire event', function () { diff --git a/typings/index.d.ts b/typings/index.d.ts index bcbc6ff3..006fadb3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -42,13 +42,13 @@ declare module 'react-draggable' { export interface DraggableCoreProps { allowAnyClick: boolean, - cancel: string, + cancel: string | React.RefObject, children?: React.ReactNode, disabled: boolean, enableUserSelectHack: boolean, offsetParent: HTMLElement, grid: [number, number], - handle: string, + handle: string | React.RefObject, nodeRef?: React.RefObject, onStart: DraggableEventHandler, onDrag: DraggableEventHandler,