react-draggable
Version:
React draggable component
290 lines (274 loc) • 7.69 kB
JavaScript
import { findInArray, isFunction, int } from './shims';
import browserPrefix, { browserPrefixToKey } from './getPrefix';
/*:: import type {ControlPosition, PositionOffsetControlPosition, MouseTouchEvent} from './types';*/
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]);
});
} // Might not be found entirely (not an Element?) - in that case, bail
// $FlowIgnore: Doesn't think elements are indexable
if (!isFunction(el[matchesSelectorFunc])) return false; // $FlowIgnore: Doesn't think elements are indexable
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*/
{
let node = el;
do {
if (matchesSelector(node, selector)) return true;
if (node === baseNode) return false;
node = node.parentNode;
} while (node);
return false;
}
export function addEvent(el
/*: ?Node*/
, event
/*: string*/
, handler
/*: Function*/
)
/*: void*/
{
if (!el) {
return;
}
if (el.attachEvent) {
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;
}
}
export function removeEvent(el
/*: ?Node*/
, event
/*: string*/
, handler
/*: Function*/
)
/*: void*/
{
if (!el) {
return;
}
if (el.detachEvent) {
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;
}
}
export function outerHeight(node
/*: HTMLElement*/
)
/*: number*/
{
// This is deliberately excluding margin for our calculations, since we are using
// offsetTop which is including margin. See getBoundPosition
let height = node.clientHeight;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
height += int(computedStyle.borderTopWidth);
height += int(computedStyle.borderBottomWidth);
return height;
}
export function outerWidth(node
/*: HTMLElement*/
)
/*: number*/
{
// This is deliberately excluding margin for our calculations, since we are using
// offsetLeft which is including margin. See getBoundPosition
let width = node.clientWidth;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
width += int(computedStyle.borderLeftWidth);
width += int(computedStyle.borderRightWidth);
return width;
}
export function innerHeight(node
/*: HTMLElement*/
)
/*: number*/
{
let height = node.clientHeight;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
height -= int(computedStyle.paddingTop);
height -= int(computedStyle.paddingBottom);
return height;
}
export function innerWidth(node
/*: HTMLElement*/
)
/*: number*/
{
let width = node.clientWidth;
const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
width -= int(computedStyle.paddingLeft);
width -= int(computedStyle.paddingRight);
return width;
} // Get from offsetParent
export function offsetXYFromParent(evt
/*: {clientX: number, clientY: number}*/
, offsetParent
/*: HTMLElement*/
)
/*: ControlPosition*/
{
const isBody = offsetParent === offsetParent.ownerDocument.body;
const offsetParentRect = isBody ? {
left: 0,
top: 0
} : offsetParent.getBoundingClientRect();
const x = evt.clientX + offsetParent.scrollLeft - offsetParentRect.left;
const y = evt.clientY + offsetParent.scrollTop - offsetParentRect.top;
return {
x,
y
};
}
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, '');
return translation;
}
export function getTranslation({
x,
y
}
/*: ControlPosition*/
, positionOffset
/*: PositionOffsetControlPosition*/
, unitSuffix
/*: string*/
)
/*: string*/
{
let translation = `translate(${x}${unitSuffix},${y}${unitSuffix})`;
if (positionOffset) {
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 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;
} // User-select Hacks:
//
// Useful for preventing blue highlights all over everything when dragging.
// 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');
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);
}
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'); // $FlowIgnore: IE
if (doc.selection) {
// $FlowIgnore: IE
doc.selection.empty();
} else {
window.getSelection().removeAllRanges(); // remove selection caused by scroll
}
} catch (e) {// probably IE
}
}
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',
...childStyle
};
}
export function addClassName(el
/*: HTMLElement*/
, className
/*: string*/
) {
if (el.classList) {
el.classList.add(className);
} else {
if (!el.className.match(new RegExp(`(?:^|\\s)${className}(?!\\S)`))) {
el.className += ` ${className}`;
}
}
}
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'), '');
}
}