// @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'), ''); } }