diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js index 69047afdf9..6d875bb4f9 100644 --- a/docs/src/ComponentsPage.js +++ b/docs/src/ComponentsPage.js @@ -1,6 +1,8 @@ /* eslint react/no-did-mount-set-state: 0 */ import React from 'react'; +import getOffset from 'dom-helpers/query/offset'; +import css from 'dom-helpers/style'; import Affix from '../../src/Affix'; import Nav from '../../src/Nav'; @@ -33,9 +35,8 @@ const ComponentsPage = React.createClass({ componentDidMount() { let elem = React.findDOMNode(this.refs.sideNav); - let domUtils = Affix.domUtils; - let sideNavOffsetTop = domUtils.getOffset(elem).top; - let sideNavMarginTop = parseInt(domUtils.getComputedStyles(elem.firstChild).marginTop, 10); + let sideNavOffsetTop = getOffset(elem).top; + let sideNavMarginTop = parseInt(css(elem.firstChild, 'marginTop'), 10); let topNavHeight = React.findDOMNode(this.refs.topNav).offsetHeight; this.setState({ diff --git a/package.json b/package.json index 84e2a3b43d..f68ca2da50 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ }, "dependencies": { "babel-runtime": "^5.8.19", - "lodash": "^3.10.0", - "classnames": "^2.1.3" + "classnames": "^2.1.3", + "dom-helpers": "^2.2.4", + "lodash": "^3.10.0" } } diff --git a/src/Affix.js b/src/Affix.js index 9648fa5166..117ee83cd3 100644 --- a/src/Affix.js +++ b/src/Affix.js @@ -1,12 +1,8 @@ import React from 'react'; import classNames from 'classnames'; import AffixMixin from './AffixMixin'; -import domUtils from './utils/domUtils'; const Affix = React.createClass({ - statics: { - domUtils - }, mixins: [AffixMixin], diff --git a/src/Collapse.js b/src/Collapse.js index ab8e4b652f..786b081cb7 100644 --- a/src/Collapse.js +++ b/src/Collapse.js @@ -16,12 +16,11 @@ const MARGINS = { function getDimensionValue(dimension, elem){ let value = elem[`offset${capitalize(dimension)}`]; - let computedStyles = domUtils.getComputedStyles(elem); let margins = MARGINS[dimension]; return (value + - parseInt(computedStyles[margins[0]], 10) + - parseInt(computedStyles[margins[1]], 10) + parseInt(domUtils.css(elem, margins[0]), 10) + + parseInt(domUtils.css(elem, margins[1]), 10) ); } diff --git a/src/Modal.js b/src/Modal.js index 48de68689f..7f8be42838 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -2,6 +2,7 @@ import React, { cloneElement } from 'react'; import classNames from 'classnames'; import domUtils from './utils/domUtils'; +import getScrollbarSize from 'dom-helpers/util/scrollbarSize'; import EventListener from './utils/EventListener'; import createChainedFunction from './utils/createChainedFunction'; import CustomPropTypes from './utils/CustomPropTypes'; @@ -67,28 +68,6 @@ function onFocus(context, handler) { return currentFocusListener; } -let scrollbarSize; - -function getScrollbarSize() { - if (scrollbarSize !== undefined) { - return scrollbarSize; - } - - let scrollDiv = document.createElement('div'); - - scrollDiv.style.position = 'absolute'; - scrollDiv.style.top = '-9999px'; - scrollDiv.style.width = '50px'; - scrollDiv.style.height = '50px'; - scrollDiv.style.overflow = 'scroll'; - - document.body.appendChild(scrollDiv); - scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; - document.body.removeChild(scrollDiv); - - scrollDiv = null; - return scrollbarSize; -} const Modal = React.createClass({ propTypes: { @@ -377,18 +356,16 @@ const Modal = React.createClass({ checkForFocus() { if (domUtils.canUseDom) { - try { - this.lastFocus = document.activeElement; - } - catch (e) {} // eslint-disable-line no-empty + this.lastFocus = domUtils.activeElement(document); } }, focusModalContent() { let modalContent = React.findDOMNode(this.refs.dialog); - let current = domUtils.activeElement(this); + let current = domUtils.activeElement(domUtils.ownerDocument(this)); let focusInModal = current && domUtils.contains(modalContent, current); + if (modalContent && this.props.autoFocus && !focusInModal) { this.lastFocus = current; modalContent.focus(); @@ -407,7 +384,7 @@ const Modal = React.createClass({ return; } - let active = domUtils.activeElement(this); + let active = domUtils.activeElement(domUtils.ownerDocument(this)); let modal = React.findDOMNode(this.refs.dialog); if (modal && modal !== active && !domUtils.contains(modal, active)) { diff --git a/src/index.js b/src/index.js index 085bc9e392..85b611af11 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +import deprecationWarning from './utils/deprecationWarning'; + export Accordion from './Accordion'; export Affix from './Affix'; export AffixMixin from './AffixMixin'; @@ -70,16 +72,36 @@ export Fade from './Collapse'; export * as FormControls from './FormControls'; +import domUtils from './utils/domUtils'; import childrenValueInputValidation from './utils/childrenValueInputValidation'; import createChainedFunction from './utils/createChainedFunction'; -import domUtils from './utils/domUtils'; import ValidComponentChildren from './utils/ValidComponentChildren'; import CustomPropTypes from './utils/CustomPropTypes'; export const utils = { childrenValueInputValidation, createChainedFunction, - domUtils, ValidComponentChildren, - CustomPropTypes + CustomPropTypes, + domUtils: createDeprecationWrapper(domUtils, 'utils/domUtils', 'npm install dom-helpers'), }; + +function createDeprecationWrapper(obj, deprecated, instead, link){ + let wrapper = {}; + + if (process.env.NODE_ENV === 'production'){ + return obj; + } + + Object.keys(obj).forEach(key => { + Object.defineProperty(wrapper, key, { + get(){ + deprecationWarning(deprecated, instead, link); + return obj[key]; + }, + set(x){ obj[key] = x; } + }); + }); + + return wrapper; +} diff --git a/src/utils/domUtils.js b/src/utils/domUtils.js index 256aaf64f8..5ffd09db5f 100644 --- a/src/utils/domUtils.js +++ b/src/utils/domUtils.js @@ -1,142 +1,33 @@ import React from 'react'; +import canUseDom from 'dom-helpers/util/inDOM'; +import getOwnerDocument from 'dom-helpers/ownerDocument'; +import getOwnerWindow from 'dom-helpers/ownerWindow'; -let canUseDom = !!( - typeof window !== 'undefined' && - window.document && - window.document.createElement -); +import contains from 'dom-helpers/query/contains'; +import activeElement from 'dom-helpers/activeElement'; +import getOffset from 'dom-helpers/query/offset'; +import offsetParent from 'dom-helpers/query/offsetParent'; +import getPosition from 'dom-helpers/query/position'; + +import css from 'dom-helpers/style'; -/** - * Get elements owner document - * - * @param {ReactComponent|HTMLElement} componentOrElement - * @returns {HTMLElement} - */ function ownerDocument(componentOrElement) { let elem = React.findDOMNode(componentOrElement); - return (elem && elem.ownerDocument) || document; + return getOwnerDocument((elem && elem.ownerDocument) || document); } function ownerWindow(componentOrElement) { let doc = ownerDocument(componentOrElement); - return doc.defaultView - ? doc.defaultView - : doc.parentWindow; -} - -/** - * get the active element, safe in IE - * @return {HTMLElement} - */ -function getActiveElement(componentOrElement){ - let doc = ownerDocument(componentOrElement); - - try { - return doc.activeElement || doc.body; - } catch (e) { - return doc.body; - } + return getOwnerWindow(doc); } -/** - * Shortcut to compute element style - * - * @param {HTMLElement} elem - * @returns {CssStyle} - */ +//TODO remove in 0.26 function getComputedStyles(elem) { return ownerDocument(elem).defaultView.getComputedStyle(elem, null); } -/** - * Get elements offset - * - * TODO: REMOVE JQUERY! - * - * @param {HTMLElement} DOMNode - * @returns {{top: number, left: number}} - */ -function getOffset(DOMNode) { - if (window.jQuery) { - return window.jQuery(DOMNode).offset(); - } - - let docElem = ownerDocument(DOMNode).documentElement; - let box = { top: 0, left: 0 }; - - // If we don't have gBCR, just use 0,0 rather than error - // BlackBerry 5, iOS 3 (original iPhone) - if ( typeof DOMNode.getBoundingClientRect !== 'undefined' ) { - box = DOMNode.getBoundingClientRect(); - } - - return { - top: box.top + window.pageYOffset - docElem.clientTop, - left: box.left + window.pageXOffset - docElem.clientLeft - }; -} - -/** - * Get elements position - * - * TODO: REMOVE JQUERY! - * - * @param {HTMLElement} elem - * @param {HTMLElement?} offsetParent - * @returns {{top: number, left: number}} - */ -function getPosition(elem, offsetParent) { - let offset, - parentOffset; - - if (window.jQuery) { - if (!offsetParent) { - return window.jQuery(elem).position(); - } - - offset = window.jQuery(elem).offset(); - parentOffset = window.jQuery(offsetParent).offset(); - - // Get element offset relative to offsetParent - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - }; - } - - parentOffset = {top: 0, left: 0}; - - // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent - if (getComputedStyles(elem).position === 'fixed' ) { - // We assume that getBoundingClientRect is available when computed position is fixed - offset = elem.getBoundingClientRect(); - - } else { - if (!offsetParent) { - // Get *real* offsetParent - offsetParent = offsetParentFunc(elem); - } - - // Get correct offsets - offset = getOffset(elem); - if ( offsetParent.nodeName !== 'HTML') { - parentOffset = getOffset(offsetParent); - } - - // Add offsetParent borders - parentOffset.top += parseInt(getComputedStyles(offsetParent).borderTopWidth, 10); - parentOffset.left += parseInt(getComputedStyles(offsetParent).borderLeftWidth, 10); - } - - // Subtract parent offsets and element margins - return { - top: offset.top - parentOffset.top - parseInt(getComputedStyles(elem).marginTop, 10), - left: offset.left - parentOffset.left - parseInt(getComputedStyles(elem).marginLeft, 10) - }; -} - /** * Get an element's size * @@ -156,57 +47,16 @@ function getSize(elem) { return rect; } -/** - * Get parent element - * - * @param {HTMLElement?} elem - * @returns {HTMLElement} - */ -function offsetParentFunc(elem) { - let docElem = ownerDocument(elem).documentElement; - let offsetParent = elem.offsetParent || docElem; - - while ( offsetParent && ( offsetParent.nodeName !== 'HTML' && - getComputedStyles(offsetParent).position === 'static' ) ) { - offsetParent = offsetParent.offsetParent; - } - - return offsetParent || docElem; -} - -/** - * Cross browser .contains() polyfill - * @param {HTMLElement} elem - * @param {HTMLElement} inner - * @return {bool} - */ -function contains(elem, inner){ - function ie8Contains(root, node) { - while (node) { - if (node === root) { - return true; - } - node = node.parentNode; - } - return false; - } - - return (elem && elem.contains) - ? elem.contains(inner) - : (elem && elem.compareDocumentPosition) - ? elem === inner || !!(elem.compareDocumentPosition(inner) & 16) - : ie8Contains(elem, inner); -} - export default { canUseDom, + css, + getComputedStyles, contains, ownerWindow, ownerDocument, - getComputedStyles, getOffset, getPosition, getSize, - activeElement: getActiveElement, - offsetParent: offsetParentFunc + activeElement, + offsetParent }; diff --git a/src/utils/index.js b/src/utils/index.js index e0048ad683..e84c9f5b33 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -2,7 +2,10 @@ import deprecationWarning from './deprecationWarning'; export childrenValueInputValidation from './childrenValueInputValidation'; export createChainedFunction from './createChainedFunction'; + +deprecationWarning('utils/domUtils', 'npm install dom-helpers'); export domUtils from './domUtils'; + export ValidComponentChildren from './ValidComponentChildren'; deprecationWarning('utils/CustomPropTypes', 'npm install react-prop-types', diff --git a/test/PositionSpec.js b/test/PositionSpec.js index ea1405fd14..a30a329620 100644 --- a/test/PositionSpec.js +++ b/test/PositionSpec.js @@ -57,7 +57,7 @@ describe('Position', function () {