201 lines
7.3 KiB
JavaScript
201 lines
7.3 KiB
JavaScript
![]() |
// @flow
|
||
|
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'), '');
|
||
|
}
|
||
|
}
|