);
}
From 704a168eb334c293cfef0d566d51999f6ed46557 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Mon, 8 Jun 2015 19:07:11 -0600
Subject: [PATCH 006/128] Release v0.24.0-alpha.1
---
CHANGELOG-alpha.md | 11 +++++++++++
package.json | 2 +-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG-alpha.md b/CHANGELOG-alpha.md
index 9c16c92cb7..ded6d073d6 100644
--- a/CHANGELOG-alpha.md
+++ b/CHANGELOG-alpha.md
@@ -1,3 +1,14 @@
+v0.24.0-alpha.1 - Tue, 09 Jun 2015 01:06:34 GMT
+-----------------------------------------------
+
+- [7211dcb](../../commit/7211dcb) [added] Add prevIcon and nextIcon props as node proptypes to Carousel
+- [5734ec3](../../commit/5734ec3) [added] Pagination component
+- [2f8c454](../../commit/2f8c454) [changed] Assert ProgressBar children can be ProgressBar only.
+- [2c46820](../../commit/2c46820) [added] `createSelectedEvent` for consistent onSelect handling
+- [c2ff9ad](../../commit/c2ff9ad) [added] property disabled on MenuItem
+
+
+
v0.24.0-alpha.0 - Tue, 02 Jun 2015 17:08:11 GMT
-----------------------------------------------
diff --git a/package.json b/package.json
index 7f8e9ddc53..9c4b05b14e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap",
- "version": "0.24.0-alpha.0",
+ "version": "0.24.0-alpha.1",
"description": "Bootstrap 3 components build with React",
"repository": {
"type": "git",
From 598b9d85820edc8eafac2f95a0d1e8bae0623485 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Tue, 9 Jun 2015 18:16:20 -0600
Subject: [PATCH 007/128] [fixed] SafeAnchor event ordering
If we prevent default before applying the `onClick` function provided in
props then we prevent elements from using the `event.preventDefault()`
mechanics for anchors as buttons. For example in the Dropdown re-work
this prevented me from having the Dropdown work when in a Nav since the
toggle is an anchor. Yet that functionality should allow uses to prevent
the Dropdown if they want in their own `onClick` handler. This will
enable such a use case.
---
src/SafeAnchor.js | 7 ++-----
test/SafeAnchorSpec.js | 16 ++++++++++++----
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/src/SafeAnchor.js b/src/SafeAnchor.js
index 8316bbe634..ae45a9c261 100644
--- a/src/SafeAnchor.js
+++ b/src/SafeAnchor.js
@@ -1,4 +1,5 @@
import React from 'react';
+import createChainedFunction from './utils/createChainedFunction';
/**
* Note: This is intended as a stop-gap for accessibility concerns that the
@@ -16,17 +17,13 @@ export default class SafeAnchor extends React.Component {
if (this.props.href === undefined) {
event.preventDefault();
}
-
- if (this.props.onClick) {
- this.props.onClick(event);
- }
}
render() {
return (
);
}
diff --git a/test/SafeAnchorSpec.js b/test/SafeAnchorSpec.js
index 75b9d0a8de..2271c2fbbe 100644
--- a/test/SafeAnchorSpec.js
+++ b/test/SafeAnchorSpec.js
@@ -43,8 +43,12 @@ describe('SafeAnchor', function() {
it('prevents default when no href is provided', function(done) {
const handleClick = (event) => {
- event.defaultPrevented.should.be.true;
- done();
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+
+ setTimeout(() => {
+ event.isDefaultPrevented().should.be.true;
+ done();
+ }, 100);
};
const instance = ReactTestUtils.renderIntoDocument();
const anchor = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'A');
@@ -54,8 +58,12 @@ describe('SafeAnchor', function() {
it('does not prevent default when href is provided', function(done) {
const handleClick = (event) => {
- expect(event.defaultPrevented).to.not.be.ok;
- done();
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+
+ setTimeout(() => {
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+ done();
+ });
};
const instance = ReactTestUtils.renderIntoDocument();
const anchor = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'A');
From 6e985b0537550d7123ef58bdff125da626acda2c Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Wed, 10 Jun 2015 19:28:05 -0600
Subject: [PATCH 008/128] [removed] Individual files in bower release
Resolves #693
---
tools/amd/bower.json | 3 +--
tools/amd/build.js | 16 ++--------------
tools/buildBabel.js | 8 +-------
3 files changed, 4 insertions(+), 23 deletions(-)
diff --git a/tools/amd/bower.json b/tools/amd/bower.json
index 0e3451d5c4..5c56aa2f4d 100644
--- a/tools/amd/bower.json
+++ b/tools/amd/bower.json
@@ -16,7 +16,6 @@
"**/.*"
],
"dependencies": {
- "react": ">= 0.13.0",
- "classnames": "^2.0.0"
+ "react": ">= 0.13.0"
}
}
diff --git a/tools/amd/build.js b/tools/amd/build.js
index ae47031c22..70f87c72e0 100644
--- a/tools/amd/build.js
+++ b/tools/amd/build.js
@@ -3,9 +3,7 @@ import path from 'path';
import fsp from 'fs-promise';
import { copy } from '../fs-utils';
import { exec } from '../exec';
-import generateFactories from '../generateFactories';
-import { repoRoot, srcRoot, bowerRoot } from '../constants';
-import { buildFolder } from '../buildBabel';
+import { repoRoot, bowerRoot } from '../constants';
const packagePath = path.join(repoRoot, 'package.json');
const bowerTemplate = path.join(__dirname, 'bower.json');
@@ -14,14 +12,6 @@ const bowerJson = path.join(bowerRoot, 'bower.json');
const readme = path.join(__dirname, 'README.md');
const license = path.join(repoRoot, 'LICENSE');
-const babelOptions = {
- __reactBootstrapDeprecationWarning: true,
- modules: 'amd'
-};
-
-const libDestination = path.join(bowerRoot, 'lib');
-const factoriesDestination = path.join(libDestination, 'factories');
-
function bowerConfig() {
return Promise.all([
fsp.readFile(packagePath)
@@ -37,11 +27,9 @@ export default function BuildBower() {
console.log('Building: '.cyan + 'bower module'.green);
return exec(`rimraf ${bowerRoot}`)
- .then(() => fsp.mkdirs(factoriesDestination))
+ .then(() => fsp.mkdirs(bowerRoot))
.then(() => Promise.all([
bowerConfig(),
- generateFactories(factoriesDestination, babelOptions),
- buildFolder(srcRoot, libDestination, babelOptions),
copy(readme, bowerRoot),
copy(license, bowerRoot)
]))
diff --git a/tools/buildBabel.js b/tools/buildBabel.js
index 7811d21bb4..e2ef5c66e2 100644
--- a/tools/buildBabel.js
+++ b/tools/buildBabel.js
@@ -12,13 +12,7 @@ export function buildContent(content, filename, destination, babelOptions={}) {
}
export function buildFile(filename, destination, babelOptions={}) {
- let content = fs.readFileSync(filename, {encoding: 'utf8'});
- if (babelOptions.__reactBootstrapDeprecationWarning) {
- content = `console.warn('This file is deprecated, and will be removed in v0.24.0. Use react-bootstrap.js or react-bootstrap.min.js instead.');
-console.warn('You can read more about it at https://github.com/react-bootstrap/react-bootstrap/issues/693');
-${content}`;
- }
-
+ const content = fs.readFileSync(filename, {encoding: 'utf8'});
if(babelUtil.canCompile(filename)) {
// Get file basename without the extension (in case not .js)
let outputName = path.basename(filename, path.extname(filename));
From 36358d6176ae9be1d4266fce20ae308602939736 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Fri, 12 Jun 2015 15:52:46 -0600
Subject: [PATCH 009/128] Release v0.24.0-alpha.2
---
CHANGELOG-alpha.md | 14 ++++++++++++++
package.json | 2 +-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG-alpha.md b/CHANGELOG-alpha.md
index ded6d073d6..62db670361 100644
--- a/CHANGELOG-alpha.md
+++ b/CHANGELOG-alpha.md
@@ -1,3 +1,17 @@
+v0.24.0-alpha.2 - Fri, 12 Jun 2015 21:52:11 GMT
+-----------------------------------------------
+
+- [9ca26e9](../../commit/9ca26e9) [added] contains "polyfill" to domUtils
+- [6e985b0](../../commit/6e985b0) [removed] Individual files in bower release
+- [3a254a1](../../commit/3a254a1) [added] Deprecation warning for individual file use in the Bower release
+- [73c7705](../../commit/73c7705) [changed] Update chai. Dev dependency.
+- [3ca90c7](../../commit/3ca90c7) [changed] Update karma-sinon-chai. Dev dependency.
+- [cc4e820](../../commit/cc4e820) [changed] Update fs-extra. Dev dependency.
+- [598b9d8](../../commit/598b9d8) [fixed] SafeAnchor event ordering
+- [beaa1fa](../../commit/beaa1fa) [changed] `PaginationButton` to use `SafeAnchor`
+
+
+
v0.24.0-alpha.1 - Tue, 09 Jun 2015 01:06:34 GMT
-----------------------------------------------
diff --git a/package.json b/package.json
index d61be1e689..b075ef7e69 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap",
- "version": "0.24.0-alpha.1",
+ "version": "0.24.0-alpha.2",
"description": "Bootstrap 3 components build with React",
"repository": {
"type": "git",
From fafe46f0037814773b5bce49f1b7b2bdf3fb3f2b Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Sat, 13 Jun 2015 12:18:33 -0400
Subject: [PATCH 010/128] [changed] Use named exports in index files
Fixes #350
---
.babelrc | 4 +-
docs/src/GettingStartedPage.js | 7 +-
docs/src/ReactPlayground.js | 10 +-
src/FormControls/index.js | 6 +-
src/Input.js | 2 +-
src/index.js | 170 +++++++++++----------------------
src/utils/index.js | 18 +---
test/FormControlsSpec.js | 2 +-
tools/public-components.js | 2 +-
9 files changed, 77 insertions(+), 144 deletions(-)
diff --git a/.babelrc b/.babelrc
index 8f85e598f6..3162e1a075 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,3 @@
{
- "optional": [
- "es7.objectRestSpread"
- ]
+ "stage": 1
}
diff --git a/docs/src/GettingStartedPage.js b/docs/src/GettingStartedPage.js
index f19af4f741..883dff20e9 100644
--- a/docs/src/GettingStartedPage.js
+++ b/docs/src/GettingStartedPage.js
@@ -43,7 +43,12 @@ $ npm install react-bootstrap`
codeText={
`var Alert = require('react-bootstrap/lib/Alert');
// or
-var Alert = require('react-bootstrap').Alert;`
+var Alert = require('react-bootstrap').Alert;
+
+// with ES6 modules
+import Alert from 'react-bootstrap/lib/Alert';
+// or
+import {Alert} from 'react-bootstrap';`
}
/>
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 0b33a304a3..743bb65979 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -3,10 +3,10 @@ import * as modClassNames from 'classnames';
import * as modAccordion from '../../src/Accordion';
import * as modAlert from '../../src/Alert';
import * as modBadge from '../../src/Badge';
-import * as modmodButton from '../../src/Button';
+import * as modButton from '../../src/Button';
import * as modButtonGroup from '../../src/ButtonGroup';
import * as modButtonInput from '../../src/ButtonInput';
-import * as modmodButtonToolbar from '../../src/ButtonToolbar';
+import * as modButtonToolbar from '../../src/ButtonToolbar';
import * as modCollapsibleNav from '../../src/CollapsibleNav';
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
import * as modCarousel from '../../src/Carousel';
@@ -56,17 +56,17 @@ const React = modReact.default;
const Accordion = modAccordion.default;
const Alert = modAlert.default;
const Badge = modBadge.default;
-const Button = modmodButton.default;
+const Button = modButton.default;
const ButtonGroup = modButtonGroup.default;
const ButtonInput = modButtonInput.default;
-const ButtonToolbar = modmodButtonToolbar.default;
+const ButtonToolbar = modButtonToolbar.default;
const CollapsibleNav = modCollapsibleNav.default;
const CollapsibleMixin = modCollapsibleMixin.default;
const Carousel = modCarousel.default;
const CarouselItem = modCarouselItem.default;
const Col = modCol.default;
const DropdownButton = modDropdownButton.default;
-const FormControls = modFormControls.default;
+const FormControls = modFormControls;
const Glyphicon = modGlyphicon.default;
const Grid = modGrid.default;
const Input = modInput.default;
diff --git a/src/FormControls/index.js b/src/FormControls/index.js
index 5a7c16286e..ef0f49c381 100644
--- a/src/FormControls/index.js
+++ b/src/FormControls/index.js
@@ -1,5 +1 @@
-import Static from './Static';
-
-export default {
- Static
-};
+export Static from './Static';
diff --git a/src/Input.js b/src/Input.js
index f385c5607d..d08eac7b35 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -1,6 +1,6 @@
import React from 'react';
import InputBase from './InputBase';
-import FormControls from './FormControls';
+import * as FormControls from './FormControls';
import deprecationWarning from './utils/deprecationWarning';
class Input extends InputBase {
diff --git a/src/index.js b/src/index.js
index 814c690572..5167ae7474 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,115 +1,57 @@
-import Accordion from './Accordion';
-import Affix from './Affix';
-import AffixMixin from './AffixMixin';
-import Alert from './Alert';
-import BootstrapMixin from './BootstrapMixin';
-import Badge from './Badge';
-import Button from './Button';
-import ButtonGroup from './ButtonGroup';
-import ButtonInput from './ButtonInput';
-import ButtonToolbar from './ButtonToolbar';
-import CollapsibleNav from './CollapsibleNav';
-import Carousel from './Carousel';
-import CarouselItem from './CarouselItem';
-import Col from './Col';
-import CollapsibleMixin from './CollapsibleMixin';
-import DropdownButton from './DropdownButton';
-import DropdownMenu from './DropdownMenu';
-import DropdownStateMixin from './DropdownStateMixin';
-import FadeMixin from './FadeMixin';
-import FormControls from './FormControls';
-import Glyphicon from './Glyphicon';
-import Grid from './Grid';
-import Input from './Input';
-import Interpolate from './Interpolate';
-import Jumbotron from './Jumbotron';
-import Label from './Label';
-import ListGroup from './ListGroup';
-import ListGroupItem from './ListGroupItem';
-import MenuItem from './MenuItem';
-import Modal from './Modal';
-import Nav from './Nav';
-import Navbar from './Navbar';
-import NavItem from './NavItem';
-import ModalTrigger from './ModalTrigger';
-import OverlayTrigger from './OverlayTrigger';
-import OverlayMixin from './OverlayMixin';
-import PageHeader from './PageHeader';
-import Pagination from './Pagination';
-import Panel from './Panel';
-import PanelGroup from './PanelGroup';
-import PageItem from './PageItem';
-import Pager from './Pager';
-import Popover from './Popover';
-import ProgressBar from './ProgressBar';
-import Row from './Row';
-import SafeAnchor from './SafeAnchor';
-import SplitButton from './SplitButton';
-import SubNav from './SubNav';
-import TabbedArea from './TabbedArea';
-import Table from './Table';
-import TabPane from './TabPane';
-import Thumbnail from './Thumbnail';
-import Tooltip from './Tooltip';
-import utils from './utils';
-import Well from './Well';
-import styleMaps from './styleMaps';
+export Accordion from './Accordion';
+export Affix from './Affix';
+export AffixMixin from './AffixMixin';
+export Alert from './Alert';
+export Badge from './Badge';
+export BootstrapMixin from './BootstrapMixin';
+export Button from './Button';
+export ButtonGroup from './ButtonGroup';
+export ButtonInput from './ButtonInput';
+export ButtonToolbar from './ButtonToolbar';
+export Carousel from './Carousel';
+export CarouselItem from './CarouselItem';
+export Col from './Col';
+export CollapsibleMixin from './CollapsibleMixin';
+export CollapsibleNav from './CollapsibleNav';
+export DropdownButton from './DropdownButton';
+export DropdownMenu from './DropdownMenu';
+export DropdownStateMixin from './DropdownStateMixin';
+export FadeMixin from './FadeMixin';
+export Glyphicon from './Glyphicon';
+export Grid from './Grid';
+export Input from './Input';
+export Interpolate from './Interpolate';
+export Jumbotron from './Jumbotron';
+export Label from './Label';
+export ListGroup from './ListGroup';
+export ListGroupItem from './ListGroupItem';
+export MenuItem from './MenuItem';
+export Modal from './Modal';
+export ModalTrigger from './ModalTrigger';
+export Nav from './Nav';
+export Navbar from './Navbar';
+export NavItem from './NavItem';
+export OverlayMixin from './OverlayMixin';
+export OverlayTrigger from './OverlayTrigger';
+export PageHeader from './PageHeader';
+export PageItem from './PageItem';
+export Pager from './Pager';
+export Pagination from './Pagination';
+export Panel from './Panel';
+export PanelGroup from './PanelGroup';
+export Popover from './Popover';
+export ProgressBar from './ProgressBar';
+export Row from './Row';
+export SafeAnchor from './SafeAnchor';
+export SplitButton from './SplitButton';
+export styleMaps from './styleMaps';
+export SubNav from './SubNav';
+export TabbedArea from './TabbedArea';
+export Table from './Table';
+export TabPane from './TabPane';
+export Thumbnail from './Thumbnail';
+export Tooltip from './Tooltip';
+export Well from './Well';
-export default {
- Accordion,
- Affix,
- AffixMixin,
- Alert,
- BootstrapMixin,
- Badge,
- Button,
- ButtonGroup,
- ButtonInput,
- ButtonToolbar,
- CollapsibleNav,
- Carousel,
- CarouselItem,
- Col,
- CollapsibleMixin,
- DropdownButton,
- DropdownMenu,
- DropdownStateMixin,
- FadeMixin,
- FormControls,
- Glyphicon,
- Grid,
- Input,
- Interpolate,
- Jumbotron,
- Label,
- ListGroup,
- ListGroupItem,
- MenuItem,
- Modal,
- Nav,
- Navbar,
- NavItem,
- ModalTrigger,
- OverlayTrigger,
- OverlayMixin,
- PageHeader,
- Panel,
- PanelGroup,
- PageItem,
- Pager,
- Pagination,
- Popover,
- ProgressBar,
- Row,
- SafeAnchor,
- SplitButton,
- SubNav,
- TabbedArea,
- Table,
- TabPane,
- Thumbnail,
- Tooltip,
- utils,
- Well,
- styleMaps
-};
+export * as FormControls from './FormControls';
+export * as utils from './utils';
diff --git a/src/utils/index.js b/src/utils/index.js
index aad9066a10..f024432a7b 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -1,13 +1,5 @@
-import childrenValueInputValidation from './childrenValueInputValidation';
-import createChainedFunction from './createChainedFunction';
-import CustomPropTypes from './CustomPropTypes';
-import domUtils from './domUtils';
-import ValidComponentChildren from './ValidComponentChildren';
-
-export default {
- childrenValueInputValidation,
- createChainedFunction,
- CustomPropTypes,
- domUtils,
- ValidComponentChildren
-};
+export childrenValueInputValidation from './childrenValueInputValidation';
+export createChainedFunction from './createChainedFunction';
+export CustomPropTypes from './CustomPropTypes';
+export domUtils from './domUtils';
+export ValidComponentChildren from './ValidComponentChildren';
diff --git a/test/FormControlsSpec.js b/test/FormControlsSpec.js
index cc56ecd618..31c9941d30 100644
--- a/test/FormControlsSpec.js
+++ b/test/FormControlsSpec.js
@@ -1,6 +1,6 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
-import FormControls from '../src/FormControls';
+import * as FormControls from '../src/FormControls';
describe('Form Controls', function () {
describe('Static', function () {
diff --git a/tools/public-components.js b/tools/public-components.js
index a030df7253..67fc67f0c7 100644
--- a/tools/public-components.js
+++ b/tools/public-components.js
@@ -1,5 +1,5 @@
import React from 'react';
-import index from '../src/index';
+import * as index from '../src/index';
let components = [];
Object.keys(index).forEach(function (item) {
From fbbb3440c0ef409613ace104791b914ca6ca61ec Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Sat, 13 Jun 2015 23:21:33 +0300
Subject: [PATCH 011/128] [fixed] bower template.
Removed "classnames" from dependencies because webpack bundles it.
---
tools/amd/bower.json | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/tools/amd/bower.json b/tools/amd/bower.json
index 5c56aa2f4d..5d22b3626e 100644
--- a/tools/amd/bower.json
+++ b/tools/amd/bower.json
@@ -8,14 +8,12 @@
"react-bootstrap.js"
],
"keywords": [
- "react",
- "react-component",
- "bootstrap"
+ <%= _.map(pkg.keywords, function(keyword) { return '"' + keyword + '"' }).join(',\n ')%>
],
"ignore": [
"**/.*"
],
"dependencies": {
- "react": ">= 0.13.0"
+ "react": "<%= pkg.peerDependencies.react %>"
}
}
From b67081b81d7c74edce6d341f8f54a880380d65b8 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Sun, 14 Jun 2015 15:21:13 -0400
Subject: [PATCH 012/128] [fixed] rootClose behavior on replaced elements
Fixes #802
---
src/OverlayMixin.js | 8 ++++--
src/RootCloseWrapper.js | 31 ++++++++++++++++----
test/OverlayTriggerSpec.js | 59 ++++++++++++++++++++++++++++++++++++--
3 files changed, 88 insertions(+), 10 deletions(-)
diff --git a/src/OverlayMixin.js b/src/OverlayMixin.js
index 76524ed61c..f8874a8828 100644
--- a/src/OverlayMixin.js
+++ b/src/OverlayMixin.js
@@ -37,7 +37,7 @@ export default {
let overlay = this.renderOverlay();
- // Save reference to help testing
+ // Save reference for future access.
if (overlay !== null) {
this._overlayInstance = React.render(overlay, this._overlayTarget);
} else {
@@ -57,7 +57,11 @@ export default {
}
if (this._overlayInstance) {
- return React.findDOMNode(this._overlayInstance);
+ if (this._overlayInstance.getWrappedDOMNode) {
+ return this._overlayInstance.getWrappedDOMNode();
+ } else {
+ return React.findDOMNode(this._overlayInstance);
+ }
}
return null;
diff --git a/src/RootCloseWrapper.js b/src/RootCloseWrapper.js
index 146c688ab9..35fecab769 100644
--- a/src/RootCloseWrapper.js
+++ b/src/RootCloseWrapper.js
@@ -4,6 +4,15 @@ import EventListener from './utils/EventListener';
// TODO: Merge this logic with dropdown logic once #526 is done.
+// TODO: Consider using an ES6 symbol here, once we use babel-runtime.
+const CLICK_WAS_INSIDE = '__click_was_inside';
+
+function suppressRootClose(event) {
+ // Tag the native event to prevent the root close logic on document click.
+ // This seems safer than using event.nativeEvent.stopImmediatePropagation(),
+ // which is only supported in IE >= 9.
+ event.nativeEvent[CLICK_WAS_INSIDE] = true;
+}
export default class RootCloseWrapper extends React.Component {
constructor(props) {
@@ -23,10 +32,8 @@ export default class RootCloseWrapper extends React.Component {
}
handleDocumentClick(e) {
- // If the click originated from within this component, don't do anything.
- // e.srcElement is required for IE8 as e.target is undefined
- let target = e.target || e.srcElement;
- if (domUtils.contains(React.findDOMNode(this), target)) {
+ // This is now the native event.
+ if (e[CLICK_WAS_INSIDE]) {
return;
}
@@ -54,7 +61,21 @@ export default class RootCloseWrapper extends React.Component {
}
render() {
- return React.Children.only(this.props.children);
+ // Wrap the child in a new element, so the child won't have to handle
+ // potentially combining multiple onClick listeners.
+ return (
+
+ {React.Children.only(this.props.children)}
+
+ );
+ }
+
+ getWrappedDOMNode() {
+ // We can't use a ref to identify the wrapped child, since we might be
+ // stealing the ref from the owner, but we know exactly the DOM structure
+ // that will be rendered, so we can just do this to get the child's DOM
+ // node for doing size calculations in OverlayMixin.
+ return React.findDOMNode(this).children[0];
}
componentWillUnmount() {
diff --git a/test/OverlayTriggerSpec.js b/test/OverlayTriggerSpec.js
index 756cb4a46d..75ae2fcf40 100644
--- a/test/OverlayTriggerSpec.js
+++ b/test/OverlayTriggerSpec.js
@@ -236,13 +236,66 @@ describe('OverlayTrigger', function() {
});
it('Should have correct isOverlayShown state', function () {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, true);
- document.documentElement.dispatchEvent(event);
+ document.documentElement.click();
+ // Need to click this way for it to propagate to document element.
instance.state.isOverlayShown.should.equal(testCase.shownAfterClick);
});
});
});
+
+ describe('replaced overlay', function () {
+ let instance;
+
+ beforeEach(function () {
+ class ReplacedOverlay extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ this.state = {replaced: false};
+ }
+
+ handleClick() {
+ this.setState({replaced: true});
+ }
+
+ render() {
+ if (this.state.replaced) {
+ return (
+
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
@@ -19,10 +22,10 @@ const MyModal = React.createClass({
magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
-
-
-
+
+
+
+
);
}
});
-const overlayTriggerInstance = (
-
- }>
-
-
- }>
-
-
-
-);
+const App = React.createClass({
+ getInitialState(){
+ return { smShow: false, lgShow: false };
+ },
+ render(){
+ let smClose = e => this.setState({ smShow: false });
+ let lgClose = e => this.setState({ lgShow: false });
-React.render(overlayTriggerInstance, mountNode);
+ return (
+
+
+
+
+
+
+
+ );
+ }
+});
+
+React.render(, mountNode);
diff --git a/docs/examples/ModalOverlayMixin.js b/docs/examples/ModalOverlayMixin.js
deleted file mode 100644
index 3a47c75b33..0000000000
--- a/docs/examples/ModalOverlayMixin.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Our custom component is managing whether the Modal is visible
-const CustomModalTrigger = React.createClass({
- mixins: [OverlayMixin],
-
- getInitialState() {
- return {
- isModalOpen: false
- };
- },
-
- handleToggle() {
- this.setState({
- isModalOpen: !this.state.isModalOpen
- });
- },
-
- render() {
- return (
-
- );
- },
-
- // This is called by the `OverlayMixin` when this component
- // is mounted or updated and the return value is appended to the body.
- renderOverlay() {
- if (!this.state.isModalOpen) {
- return ;
- }
-
- return (
-
-
- This modal is controlled by our custom trigger component.
-
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
-
-
Popover in a modal
-
TODO
-
-
Tooltips in a modal
-
TODO
-
-
-
-
Overflowing text to show scroll behavior
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
-
-
-
-
+
+
Click to get the full Modal experience!
+
+
+
+
+
+ Modal heading
+
+
+
Text in a modal
+
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
+
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
+
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
- Tight pants next level keffiyeh you probably haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel Another tooltip} href='#'>have a terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar, scenester farm-to-table banksy Austin twitter handle freegan cred raw denim single-origin coffee viral.
+ Tight pants next level keffiyeh you probably haven't
+ heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's
+ fixie sustainable quinoa 8-bit american apparel Another tooltip} href='#'>have a
+ terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four
+ loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar,
+ scenester farm-to-table banksy Austin twitter handle freegan
+ cred raw denim single-origin coffee viral.
);
diff --git a/docs/examples/TooltipPositioned.js b/docs/examples/TooltipPositioned.js
index cedb961fa9..5f94bca114 100644
--- a/docs/examples/TooltipPositioned.js
+++ b/docs/examples/TooltipPositioned.js
@@ -1,15 +1,23 @@
+
+const tooltip = (
+ Holy guacamole! Check this info.
+);
+
const positionerInstance = (
- Holy guacamole! Check this info.}>
+
- Holy guacamole! Check this info.}>
+
+
- Holy guacamole! Check this info.}>
+
+
- Holy guacamole! Check this info.}>
+
+
diff --git a/docs/generate-metadata.js b/docs/generate-metadata.js
new file mode 100644
index 0000000000..bf30c07d0e
--- /dev/null
+++ b/docs/generate-metadata.js
@@ -0,0 +1,104 @@
+import metadata from 'react-component-metadata';
+import glob from 'glob';
+import fsp from 'fs-promise';
+import promisify from '../tools/promisify';
+import marked from 'marked';
+
+marked.setOptions({
+ xhtml: true
+});
+
+let globp = promisify(glob);
+
+// removes doclet syntax from comments
+let cleanDoclets = desc => {
+ let idx = desc.indexOf('@');
+ return (idx === -1 ? desc : desc.substr(0, idx )).trim();
+};
+
+let cleanDocletValue = str => str.trim().replace(/^\{/, '').replace(/\}$/, '');
+
+
+let isLiteral = str => (/^('|")/).test(str.trim());
+
+/**
+ * parse out description doclets to an object and remove the comment
+ *
+ * @param {ComponentMetadata|PropMetadata} obj
+ */
+function parseDoclets(obj){
+ obj.doclets = metadata.parseDoclets(obj.desc || '') || {};
+ obj.desc = cleanDoclets(obj.desc || '');
+ obj.descHtml = marked(obj.desc || '');
+}
+
+/**
+ * Reads the JSDoc "doclets" and applies certain ones to the prop type data
+ * This allows us to "fix" parsing errors, or unparsable data with JSDoc style comments
+ *
+ * @param {Object} props Object Hash of the prop metadata
+ * @param {String} propName
+ */
+function applyPropDoclets(props, propName){
+ let prop = props[propName];
+ let doclets = prop.doclets;
+ let value;
+
+ // the @type doclet to provide a prop type
+ // Also allows enums (oneOf) if string literals are provided
+ // ex: @type {("optionA"|"optionB")}
+ if (doclets.type) {
+ value = cleanDocletValue(doclets.type);
+ prop.type.name = value;
+
+ if ( value[0] === '(' ) {
+ value = value.substring(1, value.length - 1).split('|');
+
+ prop.type.value = value;
+ prop.type.name = value.every(isLiteral) ? 'enum' : 'union';
+ }
+ }
+
+ // Use @required to mark a prop as required
+ // useful for custom propTypes where there isn't a `.isRequired` addon
+ if ( doclets.required) {
+ prop.required = true;
+ }
+}
+
+
+export default function generate(destination, options = { mixins: true }){
+
+ return globp(__dirname + '/../src/**/*.js') //eslint-disable-line no-path-concat
+ .then( files => {
+
+ let results = files.map(
+ filename => fsp.readFile(filename).then(content => metadata(content, options)) );
+
+ return Promise.all(results)
+ .then( data => {
+ let result = {};
+
+ data.forEach(components => {
+ Object.keys(components).forEach(key => {
+ const component = components[key];
+
+ parseDoclets(component);
+
+ Object.keys(component.props).forEach( propName => {
+ const prop = component.props[propName];
+
+ parseDoclets(prop);
+ applyPropDoclets(component.props, propName);
+ });
+ });
+
+ //combine all the component metadata into one large object
+ result = { ...result, ...components };
+ });
+
+ return result;
+ })
+ .catch( e => setTimeout(()=> { throw e; }));
+ });
+}
diff --git a/docs/server.js b/docs/server.js
index 5d0157dfca..66b49144c8 100644
--- a/docs/server.js
+++ b/docs/server.js
@@ -5,6 +5,8 @@ import path from 'path';
import Router from 'react-router';
import routes from './src/Routes';
import httpProxy from 'http-proxy';
+
+import metadata from './generate-metadata';
import ip from 'ip';
const development = process.env.NODE_ENV !== 'production';
@@ -21,20 +23,27 @@ if (development) {
proxy.web(req, res, { target });
});
- app.use(function renderApp(req, res) {
- res.header('Access-Control-Allow-Origin', target);
- res.header('Access-Control-Allow-Headers', 'X-Requested-With');
-
- Router.run(routes, req.url, Handler => {
- let html = React.renderToString();
- res.send('' + html);
- });
- });
-
proxy.on('error', function(e) {
console.log('Could not connect to webpack proxy'.red);
console.log(e.toString().red);
});
+
+ console.log('Prop data generation started:'.green);
+
+ metadata().then( props => {
+ console.log('Prop data generation finished:'.green);
+
+ app.use(function renderApp(req, res) {
+ res.header('Access-Control-Allow-Origin', target);
+ res.header('Access-Control-Allow-Headers', 'X-Requested-With');
+
+ Router.run(routes, req.url, Handler => {
+ let html = React.renderToString();
+ res.send('' + html);
+ });
+ });
+ });
+
} else {
app.use(express.static(path.join(__dirname, '../docs-built')));
}
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 93aa06266e..c3166eca4d 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -1,4 +1,4 @@
-/* eslint no-path-concat: 0, react/no-did-mount-set-state: 0 */
+/* eslint react/no-did-mount-set-state: 0 */
import React from 'react';
@@ -9,6 +9,7 @@ import NavItem from '../../src/NavItem';
import NavMain from './NavMain';
import PageHeader from './PageHeader';
+import PropTable from './PropTable';
import PageFooter from './PageFooter';
import ReactPlayground from './ReactPlayground';
import Samples from './Samples';
@@ -67,6 +68,7 @@ const ComponentsPage = React.createClass({
flush against each other. To preserve the spacing between multiple inline buttons, wrap your
button group in {''}.
+
Sizes
Fancy larger or smaller buttons? Add bsSize="large", bsSize="small", or bsSize="xsmall" for additional sizes.
@@ -99,6 +101,10 @@ const ComponentsPage = React.createClass({
feedback as to the loading state, this can easily be done by updating
your {''}’s props from a state change like below.
CollapsibleMixin can be used to create your own components with collapse functionality.
+
+
Props
+
+
Panels, Accordion
+
+
+
PanelGroup
+
+
Modals Modal
A static example
-
A rendered modal with header, body, and set of actions in the footer.
-
The header is added automatically if you pass in a title prop.
+
+ A rendered modal with header, body, and set of actions in the footer. The {''} Component comes with
+ a few convenient "sub components": {''}, {''}, {''},
+ and {''}, which you can use to build the Modal content.
+
+
+
Additional Import Options
+
+ The Modal Header, Title, Body, and Footer components are available as static properties the {''} component, but you can also,
+ import them directly from the /lib directory like: {"require('react-bootstrap/lib/ModalHeader')"}.
+
+
Live demo
-
Use <ModalTrigger /> to create a real modal that's added to the document body when opened.
+
Use {''} in combination with other components to show or hide your Modal.
-
Custom trigger
-
Use OverlayMixin in a custom component to manage the modal's state yourself.
-
-
-
Contained Modal
+
Contained Modal
You will need to add the following css to your project and ensure that your container has the modal-container class.
You can apply custom css to the modal dialog div using the "dialogClassName" prop. Example is using a custom css class with width set to 90%.
+
+
Props
+
+
Modal
+
+
+
Modal.Header
+
+
+
Modal.Title
+
+
+
Modal.Body
+
+
+
Modal.Footer
+
+
+
ModalTrigger Deprecated: use the Modal directly to manage it's visibility
+
+
+
{/* Tooltip */}
-
Tooltips Tooltip
-
Example tooltips
-
-
Tooltip component.
-
+
Tooltip
+
+ Tooltip component for a more stylish alternative to that anchor tag title attribute.
+
+
-
Positioned tooltip component.
+
Attach and position tooltips with OverlayTrigger.
-
Positioned tooltip in copy.
+
Positioned tooltip in text copy.
+
+
Props
+
+
Overlay Trigger
+
+
+
Tooltip
+
{/* Popover */}
-
Popovers Popover
-
Example popovers
+
Popovers
-
Popover component.
-
+
+ The Popover, offers a more robust alternative to the Tooltip for displaying overlays of content.
+
+
-
Positioned popover component.
+
The Popover component, like the Tooltip can be used with an OverlayTrigger Component, and positioned around it.
Trigger behaviors. It's inadvisable to use "hover" or "focus" triggers for popovers, because they have poor accessibility from keyboard and on mobile devices.
Positioned popover components in scrolling container.
+
+
Props
+
+
+
+
+ {/* Overlay */}
+
+
Overlay
+
+
+ The OverlayTrigger component is great for most use cases, but as a higher level abstraction it can lack the flexibility needed
+ to build more nuanced or custom behaviors into your Overlay components. For these cases it can be helpful to forgo the trigger and use
+ the Overlay component directly.
+
+
+
+
+ You don't need to use the provided Tooltip or Popover components. Creating custom overlays
+ is as easy as wrapping some markup in an Overlay component
+
Add responsive prop to make them scroll horizontally up to small devices (under 768px). When viewing on anything larger than 768px wide, you will not see any difference in these tables.
If type is not set, child element(s) will be rendered instead of an input element.
getValue() will not work when used this way.
+
+
Props
+
+
+ {/* Utilities */}
+
+
Utilities Portal, Position
+
+
Portal
+
+ A Component that renders its children into a new React "subtree" or container. The Portal component kind of like the React
+ equivalent to jQuery's .appendTo(), which is helpful for components that need to be appended to a DOM node other than
+ the component's direct parent. The Modal, and Overlay components use the Portal component internally.
+
+
Props
+
+
+
+
Position
+
+ A Component that absolutely positions its child to a target component or DOM node. Useful for creating custom
+ popups or tooltips. Used by the Overlay Components.
+
+ );
+ });
+ },
+
+ renderRequiredLabel(prop) {
+ if (!prop.required) {
+ return null;
+ }
+
+ return (
+
+ );
+ },
+
+ getType(prop) {
+ let type = prop.type || {};
+ let name = this.getDisplayTypeName(type.name);
+ let doclets = prop.doclets || {};
+
+ switch (name) {
+ case 'object':
+ return name;
+ case 'union':
+ return type.value.reduce((current, val, i, list) => {
+ let item = this.getType({ type: val });
+ if (React.isValidElement(item)) {
+ item = React.cloneElement(item, {key: i});
+ }
+ current = current.concat(item);
+
+ return i === (list.length - 1) ? current : current.concat(' | ');
+ }, []);
+ case 'array':
+ let child = this.getType({ type: type.value });
+
+ return {'array<'}{ child }{'>'};
+ case 'enum':
+ return this.renderEnum(type);
+ case 'custom':
+ return cleanDocletValue(doclets.type || name);
+ default:
+ return name;
+ }
+ },
+
+ getDisplayTypeName(typeName) {
+ if (typeName === 'func') {
+ return 'function';
+ } else if (typeName === 'bool') {
+ return 'boolean';
+ } else {
+ return typeName;
+ }
+ },
+
+ renderEnum(enumType) {
+ const enumValues = enumType.value || [];
+
+ const renderedEnumValues = [];
+ enumValues.forEach(function renderEnumValue(enumValue, i) {
+ if (i > 0) {
+ renderedEnumValues.push(
+ ,
+ );
+ }
+
+ renderedEnumValues.push(
+ {enumValue}
+ );
+ });
+
+ return (
+ one of: {renderedEnumValues}
+ );
+ }
+});
+
+
+
+export default PropTable;
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 743bb65979..02e170c48f 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -36,6 +36,7 @@ import * as modPagination from '../../src/Pagination';
import * as modPanel from '../../src/Panel';
import * as modPanelGroup from '../../src/PanelGroup';
import * as modPopover from '../../src/Popover';
+//import * as modPopoverTrigger from '../../src/PopoverTrigger';
import * as modProgressBar from '../../src/ProgressBar';
import * as modRow from '../../src/Row';
import * as modSplitButton from '../../src/SplitButton';
@@ -44,14 +45,22 @@ import * as modTable from '../../src/Table';
import * as modTabPane from '../../src/TabPane';
import * as modThumbnail from '../../src/Thumbnail';
import * as modTooltip from '../../src/Tooltip';
+//import * as modTooltipTrigger from '../../src/TooltipTrigger';
import * as modWell from '../../src/Well';
+import * as modPortal from '../../src/Portal';
+import * as modOverlay from '../../src/Overlay';
+
import babel from 'babel-core/browser';
import CodeExample from './CodeExample';
+
+
const classNames = modClassNames.default;
/* eslint-disable */
+const Portal = modPortal.default;
+
const React = modReact.default;
const Accordion = modAccordion.default;
const Alert = modAlert.default;
@@ -89,6 +98,7 @@ const Pager = modPager.default;
const Panel = modPanel.default;
const PanelGroup = modPanelGroup.default;
const Popover = modPopover.default;
+//const PopoverTrigger = modPopoverTrigger.default;
const ProgressBar = modProgressBar.default;
const Row = modRow.default;
const SplitButton = modSplitButton.default;
@@ -97,7 +107,10 @@ const Table = modTable.default;
const TabPane = modTabPane.default;
const Thumbnail = modThumbnail.default;
const Tooltip = modTooltip.default;
+//const TooltipTrigger = modTooltipTrigger.default;
const Well = modWell.default;
+const Overlay = modOverlay.default;
+
/* eslint-enable */
const IS_MOBILE = typeof navigator !== 'undefined' && (
diff --git a/docs/src/Root.js b/docs/src/Root.js
index f3ce4b2b76..449db4528e 100644
--- a/docs/src/Root.js
+++ b/docs/src/Root.js
@@ -14,7 +14,8 @@ const Root = React.createClass({
'index.html',
'introduction.html',
'getting-started.html',
- 'components.html'
+ 'components.html',
+ 'support.html'
];
}
},
@@ -25,6 +26,14 @@ const Root = React.createClass({
};
},
+ childContextTypes: {
+ metadata: React.PropTypes.object
+ },
+
+ getChildContext(){
+ return { metadata: this.props.propData };
+ },
+
render() {
// Dump out our current props to a global object via a script tag so
// when initialising the browser environment we can bootstrap from the
@@ -64,7 +73,7 @@ const Root = React.createClass({
-
+
diff --git a/docs/src/Routes.js b/docs/src/Routes.js
index 790880e163..ae95030507 100644
--- a/docs/src/Routes.js
+++ b/docs/src/Routes.js
@@ -5,6 +5,7 @@ import HomePage from './HomePage';
import IntroductionPage from './IntroductionPage';
import GettingStartedPage from './GettingStartedPage';
import ComponentsPage from './ComponentsPage';
+import SupportPage from './SupportPage';
import NotFoundPage from './NotFoundPage';
import {Route, DefaultRoute, NotFoundRoute} from 'react-router';
@@ -17,5 +18,6 @@ export default (
+
);
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index c9b0aee582..47e6720802 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -32,7 +32,7 @@ export default {
CollapsibleParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsibleParagraph.js', 'utf8'),
ModalStatic: require('fs').readFileSync(__dirname + '/../examples/ModalStatic.js', 'utf8'),
ModalTrigger: require('fs').readFileSync(__dirname + '/../examples/ModalTrigger.js', 'utf8'),
- ModalOverlayMixin: require('fs').readFileSync(__dirname + '/../examples/ModalOverlayMixin.js', 'utf8'),
+
ModalContained: require('fs').readFileSync(__dirname + '/../examples/ModalContained.js', 'utf8'),
ModalDefaultSizing: require('fs').readFileSync(__dirname + '/../examples/ModalDefaultSizing.js', 'utf8'),
ModalCustomSizing: require('fs').readFileSync(__dirname + '/../examples/ModalCustomSizing.js', 'utf8'),
@@ -99,5 +99,8 @@ export default {
InputValidation: require('fs').readFileSync(__dirname + '/../examples/InputValidation.js', 'utf8'),
InputHorizontal: require('fs').readFileSync(__dirname + '/../examples/InputHorizontal.js', 'utf8'),
InputWrapper: require('fs').readFileSync(__dirname + '/../examples/InputWrapper.js', 'utf8'),
- MenuItem: require('fs').readFileSync(__dirname + '/../examples/MenuItem.js', 'utf8')
+ MenuItem: require('fs').readFileSync(__dirname + '/../examples/MenuItem.js', 'utf8'),
+
+ Overlay: require('fs').readFileSync(__dirname + '/../examples/Overlay.js', 'utf8'),
+ OverlayCustom: require('fs').readFileSync(__dirname + '/../examples/OverlayCustom.js', 'utf8')
};
diff --git a/docs/src/SupportPage.js b/docs/src/SupportPage.js
new file mode 100644
index 0000000000..0a792dfbd5
--- /dev/null
+++ b/docs/src/SupportPage.js
@@ -0,0 +1,48 @@
+import React from 'react';
+
+import NavMain from './NavMain';
+import PageHeader from './PageHeader';
+import PageFooter from './PageFooter';
+
+export default class Page extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
Stay up to date on the development of React-Bootstrap and reach out to the community with these helpful resources.
+
+
Stack Overflow
+
Ask questions about specific problems you have faced, including details about what exactly you are trying to do. Make sure you tag your question with react-bootstrap. You can also read through existing React-Bootstrap questions.
+
+
Live help
+
Bring your questions and pair with other react-bootstrap users in a live Thinkful hangout. Hear about the challenges other developers are running into, or screenshare your own code with the group for feedback.
The issue tracker is the preferred channel for bug reports, features requests and submitting pull requests. See more about how we use issues in the contribution guidelines.
+
);
}
});
diff --git a/src/Modal.js b/src/Modal.js
index 3a28a7ff62..70b5945bab 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -1,15 +1,21 @@
-import React from 'react';
+/*eslint-disable react/prop-types */
+import React, { cloneElement } from 'react';
+
import classNames from 'classnames';
+import createChainedFunction from './utils/createChainedFunction';
import BootstrapMixin from './BootstrapMixin';
import FadeMixin from './FadeMixin';
import domUtils from './utils/domUtils';
import EventListener from './utils/EventListener';
+import deprecationWarning from './utils/deprecationWarning';
+
+import Portal from './Portal';
+import Body from './ModalBody';
+import Header from './ModalHeader';
+import Title from './ModalTitle';
+import Footer from './ModalFooter';
-// TODO:
-// - aria-labelledby
-// - Add `modal-body` div if only one child passed in that doesn't already have it
-// - Tests
/**
* Gets the correct clientHeight of the modal container
@@ -31,9 +37,32 @@ function getContainer(context){
domUtils.ownerDocument(context).body;
}
+function requiredIfNot(key, type){
+ return function(props, propName, componentName){
+ let propType = type;
+
+ if ( props[ key] === undefined ){
+ propType = propType.isRequired;
+ }
+ return propType(props, propName, componentName);
+ };
+}
+
+function toChildArray(children){
+ let result = [];
+ React.Children.forEach(children, c => result.push(c));
+ return result;
+}
+
+
+let currentFocusListener;
+
/**
* Firefox doesn't have a focusin event so using capture is easiest way to get bubbling
* IE8 can't do addEventListener, but does have onfocusin, so we use that in ie8
+ *
+ * We only allow one Listener at a time to avoid stack overflows
+ *
* @param {ReactElement|HTMLElement} context
* @param {Function} handler
*/
@@ -42,6 +71,10 @@ function onFocus(context, handler) {
let useFocusin = !doc.addEventListener;
let remove;
+ if ( currentFocusListener ) {
+ currentFocusListener.remove();
+ }
+
if (useFocusin) {
document.attachEvent('onfocusin', handler);
remove = () => document.detachEvent('onfocusin', handler);
@@ -49,7 +82,10 @@ function onFocus(context, handler) {
document.addEventListener('focus', handler, true);
remove = () => document.removeEventListener('focus', handler, true);
}
- return { remove };
+
+ currentFocusListener = { remove };
+
+ return currentFocusListener;
}
let scrollbarSize;
@@ -75,19 +111,64 @@ function getScrollbarSize(){
}
-const Modal = React.createClass({
+const ModalMarkup = React.createClass({
mixins: [BootstrapMixin, FadeMixin],
propTypes: {
+ /**
+ * The Modal title text
+ * @deprecated Use the "Modal.Header" component instead
+ */
title: React.PropTypes.node,
+ /**
+ * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked.
+ */
backdrop: React.PropTypes.oneOf(['static', true, false]),
+ /**
+ * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked.
+ */
keyboard: React.PropTypes.bool,
+
+ /**
+ * Specify whether the Modal heading should contain a close button
+ * @deprecated Use the "Modal.Header" Component instead
+ */
closeButton: React.PropTypes.bool,
- container: React.PropTypes.object,
+
+ /**
+ * Open and close the Modal with a slide and fade animation.
+ */
animation: React.PropTypes.bool,
- onRequestHide: React.PropTypes.func.isRequired,
+ /**
+ * A Callback fired when the header closeButton or non-static backdrop is clicked.
+ * @type {function}
+ * @required
+ */
+ onHide: requiredIfNot('onRequestHide', React.PropTypes.func),
+
+ /**
+ * A Callback fired when the header closeButton or non-static backdrop is clicked.
+ * @deprecated Replaced by `onHide`.
+ */
+ onRequestHide: React.PropTypes.func,
+
+ /**
+ * A css class to apply to the Modal dialog DOM node.
+ */
dialogClassName: React.PropTypes.string,
+
+ /**
+ * When `true` The modal will automatically shift focus to itself when it opens, and replace it to the last focused element when it closes.
+ * Generally this should never be set to false as it makes the Modal less accessible to assistive technologies, like screen-readers.
+ */
+ autoFocus: React.PropTypes.bool,
+
+ /**
+ * When `true` The modal will prevent focus from leaving the Modal while open.
+ * Consider leaving the default value here, as it is necessary to make the Modal work well with assistive technologies,
+ * such as screen readers.
+ */
enforceFocus: React.PropTypes.bool
},
@@ -98,6 +179,8 @@ const Modal = React.createClass({
keyboard: true,
animation: true,
closeButton: true,
+
+ autoFocus: true,
enforceFocus: true
};
},
@@ -131,9 +214,8 @@ const Modal = React.createClass({
onClick={this.props.backdrop === true ? this.handleBackdropClick : null}
ref="modal">
+ );
+ }
+}
+
+//used in liue of parent contexts right now to auto wire the close button
+ModalHeader.__isModalHeader = true;
+
+ModalHeader.propTypes = {
+ /**
+ * A css class applied to the Component
+ */
+ modalClassName: React.PropTypes.string,
+ /**
+ * Specify whether the Component should contain a close button
+ */
+ closeButton: React.PropTypes.bool,
+ /**
+ * A Callback fired when the close button is clicked. If used directly inside a Modal component, the onHide will automatically
+ * be propagated up to the parent Modal `onHide`.
+ */
+ onHide: React.PropTypes.func
+};
+
+ModalHeader.defaultProps = {
+ modalClassName: 'modal-header',
+ closeButton: false
+};
+
+
+export default ModalHeader;
diff --git a/src/ModalTitle.js b/src/ModalTitle.js
new file mode 100644
index 0000000000..0c44bbaee5
--- /dev/null
+++ b/src/ModalTitle.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import classnames from 'classnames';
+
+class ModalTitle extends React.Component {
+
+ render() {
+ return (
+
}
trigger='click' rootClose={testCase.rootClose}
- >
-
+ >
+
);
const overlayTrigger = React.findDOMNode(instance);
diff --git a/test/PortalSpec.js b/test/PortalSpec.js
new file mode 100644
index 0000000000..66a1b49fc0
--- /dev/null
+++ b/test/PortalSpec.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import Portal from '../src/Portal';
+
+describe('Portal', function () {
+ let instance;
+
+ let Overlay = React.createClass({
+ render() {
+ return (
+
+ {this.props.overlay}
+
+ );
+ },
+ getOverlayDOMNode(){
+ return this.refs.p.getOverlayDOMNode();
+ }
+ });
+
+ afterEach(function() {
+ if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
+ React.unmountComponentAtNode(React.findDOMNode(instance));
+ }
+ });
+
+ it('Should render overlay into container (DOMNode)', function() {
+ let container = document.createElement('div');
+
+ instance = ReactTestUtils.renderIntoDocument(
+ } />
+ );
+
+ assert.equal(container.querySelectorAll('#test1').length, 1);
+ });
+
+ it('Should render overlay into container (ReactComponent)', function() {
+ let Container = React.createClass({
+ render() {
+ return } />;
+ }
+ });
+
+ instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1);
+ });
+
+ it('Should not render a null overlay', function() {
+ let Container = React.createClass({
+ render() {
+ return ;
+ }
+ });
+
+ instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ assert.equal(instance.refs.overlay.getOverlayDOMNode(), null);
+ });
+
+ it('Should render only an overlay', function() {
+ let OnlyOverlay = React.createClass({
+ render() {
+ return {this.props.overlay};
+ }
+ });
+
+ let overlayInstance = ReactTestUtils.renderIntoDocument(
+ } />
+ );
+
+ assert.equal(overlayInstance.refs.p.getOverlayDOMNode().nodeName, 'DIV');
+ });
+});
diff --git a/test/index.js b/test/index.js
index b3998914c4..73c1d4ba3e 100644
--- a/test/index.js
+++ b/test/index.js
@@ -19,5 +19,9 @@ describe('Process environment for tests', function () {
});
});
+// Ensure all files in src folder are loaded for proper code coverage analysis
+const srcContext = require.context('../src', true, /.*\.js$/);
+srcContext.keys().forEach(srcContext);
+
const testsContext = require.context('.', true, /Spec$/);
testsContext.keys().forEach(testsContext);
diff --git a/test/utils/CustomPropTypesSpec.js b/test/utils/CustomPropTypesSpec.js
index 9e4e78de07..ef6d8e152e 100644
--- a/test/utils/CustomPropTypesSpec.js
+++ b/test/utils/CustomPropTypesSpec.js
@@ -2,46 +2,82 @@ import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import CustomPropTypes from '../../src/utils/CustomPropTypes';
+function isChainableAndUndefinedOK(validatorUnderTest) {
+ it('Should validate OK with undefined or null values', function() {
+ assert.isUndefined(validatorUnderTest({}, 'p', 'Component'));
+ assert.isUndefined(validatorUnderTest({p: null}, 'p', 'Component'));
+ });
+
+ it('Should be able to chain', function() {
+ let err = validatorUnderTest.isRequired({}, 'p', 'Component');
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Required prop');
+ assert.include(err.message, 'was not specified in');
+ });
+}
+
describe('CustomPropTypes', function() {
describe('mountable', function () {
function validate(prop) {
return CustomPropTypes.mountable({p: prop}, 'p', 'Component');
}
- function validateRequired(prop) {
- return CustomPropTypes.mountable.isRequired({p: prop}, 'p', 'Component');
- }
+
+ isChainableAndUndefinedOK(CustomPropTypes.mountable);
it('Should return error with non mountable values', function() {
- assert.instanceOf(validateRequired(), Error);
- assert.instanceOf(validateRequired(null), Error);
- assert.instanceOf(validate({}), Error);
+ let err = validate({});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'expected a DOM element or an object that has a `render` method');
});
+
it('Should return undefined with mountable values', function() {
- assert.isUndefined(validate());
- assert.isUndefined(validate(null));
assert.isUndefined(validate(document.createElement('div')));
assert.isUndefined(validate(document.body));
assert.isUndefined(validate(ReactTestUtils.renderIntoDocument()));
});
});
+ describe('elementType', function () {
+ function validate(prop) {
+ return CustomPropTypes.elementType({p: prop}, 'p', 'TestComponent');
+ }
+
+ isChainableAndUndefinedOK(CustomPropTypes.elementType);
+
+ it('Should validate OK with elementType values', function() {
+ assert.isUndefined(validate('span'));
+ assert.isUndefined(validate(function(){}));
+ });
+
+ it('Should return error with not a string or function values', function() {
+ let err = validate({});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Expected an Element `type` such as a tag name or return value of React.createClass(...)');
+ });
+
+ it('Should return error with react element', function() {
+ let err = validate(React.createElement('span'));
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Expected an Element `type`, not an actual Element');
+ });
+ });
+
describe('keyOf', function () {
let obj = {'foo': 1};
function validate(prop) {
return CustomPropTypes.keyOf(obj)({p: prop}, 'p', 'Component');
}
- function validateRequired(prop) {
- return CustomPropTypes.keyOf(obj).isRequired({p: prop}, 'p', 'Component');
- }
+
+ isChainableAndUndefinedOK(CustomPropTypes.keyOf(obj));
it('Should return error with non-key values', function() {
- assert.instanceOf(validateRequired(), Error);
- assert.instanceOf(validateRequired(null), Error);
- assert.instanceOf(validate('bar'), Error);
+ let err = validate('bar');
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'expected one of ["foo"]');
});
- it('Should return undefined with key values', function() {
- assert.isUndefined(validate());
+
+ it('Should validate OK with key values', function() {
assert.isUndefined(validate('foo'));
obj.bar = 2;
assert.isUndefined(validate('bar'));
@@ -55,16 +91,16 @@ describe('CustomPropTypes', function() {
return CustomPropTypes.singlePropFrom(propList)(testProps, 'value', 'Component');
}
- it('Should return undefined if only one listed prop in used', function () {
+ it('Should validate OK if only one listed prop in used', function () {
const testProps = {value: 5};
assert.isUndefined(validate(testProps));
});
it('Should return error if multiple of the listed properties have values', function () {
- const testProps = {value: 5, children: 5};
-
- validate(testProps).should.be.instanceOf(Error);
+ let err = validate({value: 5, children: 5});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'only one of the following may be provided: value and children');
});
});
diff --git a/test/utils/overlayPositionUtilsSpec.js b/test/utils/overlayPositionUtilsSpec.js
new file mode 100644
index 0000000000..50703e4b4c
--- /dev/null
+++ b/test/utils/overlayPositionUtilsSpec.js
@@ -0,0 +1,92 @@
+import position from '../../src/utils/overlayPositionUtils';
+
+describe('calcOverlayPosition()', function() {
+ [
+ {
+ placement: 'left',
+ noOffset: [50, 300, null, '50%'],
+ offsetBefore: [-200, 150, null, '0%'],
+ offsetAfter: [300, 450, null, '100%']
+ },
+ {
+ placement: 'top',
+ noOffset: [200, 150, '50%', null],
+ offsetBefore: [50, -100, '0%', null],
+ offsetAfter: [350, 400, '100%', null]
+ },
+ {
+ placement: 'bottom',
+ noOffset: [200, 450, '50%', null],
+ offsetBefore: [50, 200, '0%', null],
+ offsetAfter: [350, 700, '100%', null]
+ },
+ {
+ placement: 'right',
+ noOffset: [350, 300, null, '50%'],
+ offsetBefore: [100, 150, null, '0%'],
+ offsetAfter: [600, 450, null, '100%']
+ }
+ ].forEach(function(testCase) {
+
+ describe(`placement = ${testCase.placement}`, function() {
+ let overlayStub, padding, placement;
+
+ beforeEach(function() {
+ placement = testCase.placement;
+ padding = 50;
+ overlayStub = {
+ offsetHeight: 200, offsetWidth: 200
+ };
+
+ position.getContainerDimensions = sinon.stub().returns({
+ width: 600, height: 600, scroll: 100
+ });
+ });
+
+ function checkPosition(expected) {
+ const [
+ positionLeft,
+ positionTop,
+ arrowOffsetLeft,
+ arrowOffsetTop
+ ] = expected;
+
+ it('Should calculate the correct position', function() {
+ position.calcOverlayPosition(placement, overlayStub, {}, {}, padding).should.eql(
+ { positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop }
+ );
+ });
+ }
+
+ describe('no viewport offset', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 250, top: 350, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.noOffset);
+ });
+
+ describe('viewport offset before', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 0, top: 100, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.offsetBefore);
+ });
+
+ describe('viewport offset after', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 500, top: 600, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.offsetAfter);
+ });
+ });
+ });
+ });
diff --git a/tools/promisify.js b/tools/promisify.js
new file mode 100644
index 0000000000..649829c506
--- /dev/null
+++ b/tools/promisify.js
@@ -0,0 +1,17 @@
+
+export default function promisify(fn){
+ return function (...args){
+ return new Promise(function(resolve, reject){
+
+ function finish(err, result){
+ if (err) {
+ return reject(err);
+ }
+ resolve(result);
+ }
+
+ fn.apply(null, args.concat(finish));
+ });
+
+ };
+}
From 4fb7e0d64b4ef3d2c97157213736ea7a5aa76d47 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sun, 5 Jul 2015 21:09:59 -0400
Subject: [PATCH 022/128] [changed] Remove Overlay and Modal deprecations
---
docs/examples/{ModalTrigger.js => Modal.js} | 0
docs/examples/ModalCustomSizing.js | 80 ++++---
docs/src/ComponentsPage.js | 2 +-
docs/src/ReactPlayground.js | 11 +-
docs/src/Samples.js | 2 +-
src/Input.js | 2 +-
src/Modal.js | 95 ++------
src/ModalTrigger.js | 121 ----------
src/OverlayMixin.js | 95 --------
src/OverlayTrigger.js | 65 ++----
src/Portal.js | 77 +++++-
src/index.js | 2 -
test/FactoriesSpec.js | 2 +-
test/ModalSpec.js | 244 ++++++++------------
test/ModalTriggerSpec.js | 123 ----------
test/OverlayMixinSpec.js | 89 -------
test/helpers.js | 25 ++
test/server/ModalSpec.js | 2 +-
18 files changed, 278 insertions(+), 759 deletions(-)
rename docs/examples/{ModalTrigger.js => Modal.js} (100%)
delete mode 100644 src/ModalTrigger.js
delete mode 100644 src/OverlayMixin.js
delete mode 100644 test/ModalTriggerSpec.js
delete mode 100644 test/OverlayMixinSpec.js
diff --git a/docs/examples/ModalTrigger.js b/docs/examples/Modal.js
similarity index 100%
rename from docs/examples/ModalTrigger.js
rename to docs/examples/Modal.js
diff --git a/docs/examples/ModalCustomSizing.js b/docs/examples/ModalCustomSizing.js
index 4122e352d4..701d67338a 100644
--- a/docs/examples/ModalCustomSizing.js
+++ b/docs/examples/ModalCustomSizing.js
@@ -1,40 +1,52 @@
-const MyModal = React.createClass({
- render() {
+const Example = React.createClass({
+
+ getInitialState(){
+ return { show: false };
+ },
+
+ render(){
+ let close = e => this.setState({ show: false });
+
return (
-
-
- Modal heading
-
-
-
Wrapped Text
-
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
- Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
- odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis.
- Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus
- fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum
- fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum
- repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia
- cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos.
- Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat
- reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae
- distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat
- ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit
- magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
- magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
- similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
-
-
-
-
-
+
+
+
+
+
+ Modal heading
+
+
+
Wrapped Text
+
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
+ Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
+ odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis.
+ Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus
+ fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum
+ fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum
+ repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia
+ cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos.
+ Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat
+ reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae
+ distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat
+ ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit
+ magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
+ magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
+ similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
} onMouseOver={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.mouseOver(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onMouseOut prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onMouseOut={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.mouseOut(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onFocus prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onFocus={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.focus(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onBlur prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onBlur={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.blur(modalTrigger);
- callback.called.should.be.true;
- });
-
- // This is just a copy of the test case for OverlayTrigger.
- it('Should forward requested context', function() {
- const contextTypes = {
- key: React.PropTypes.string
- };
-
- const contextSpy = sinon.spy();
- class ContextReader extends React.Component {
- render() {
- contextSpy(this.context.key);
- return ;
- }
- }
- ContextReader.contextTypes = contextTypes;
-
- const TriggerWithContext = ModalTrigger.withContext(contextTypes);
- class ContextHolder extends React.Component {
- getChildContext() {
- return {key: 'value'};
- }
-
- render() {
- return (
- }
- >
-
-
- );
- }
- }
- ContextHolder.childContextTypes = contextTypes;
-
- const instance = ReactTestUtils.renderIntoDocument();
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.click(modalTrigger);
-
- contextSpy.calledWith('value').should.be.true;
- });
-});
diff --git a/test/OverlayMixinSpec.js b/test/OverlayMixinSpec.js
deleted file mode 100644
index 66818d48f5..0000000000
--- a/test/OverlayMixinSpec.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import ReactTestUtils from 'react/lib/ReactTestUtils';
-import OverlayMixin from '../src/OverlayMixin';
-import { shouldWarn } from './helpers';
-
-describe('OverlayMixin', function () {
- let instance;
-
- let Overlay = React.createClass({
- mixins: [OverlayMixin],
-
- render() {
- return ;
- },
-
- renderOverlay() {
- return this.props.overlay;
- }
- });
-
-
- afterEach(function() {
- if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
- React.unmountComponentAtNode(React.findDOMNode(instance));
- }
-
- if ( console.warn.called ) {
- shouldWarn('Overlay mixin is deprecated');
- }
- });
-
- it('Should render overlay into container (DOMNode)', function() {
- let container = document.createElement('div');
-
- instance = ReactTestUtils.renderIntoDocument(
- } />
- );
-
- assert.equal(container.querySelectorAll('#test1').length, 1);
- });
-
- it('Should render overlay into container (ReactComponent)', function() {
- let Container = React.createClass({
- render() {
- return } />;
- }
- });
-
- instance = ReactTestUtils.renderIntoDocument(
-
- );
-
- assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1);
- });
-
- it('Should not render a null overlay', function() {
- let Container = React.createClass({
- render() {
- return ;
- }
- });
-
- instance = ReactTestUtils.renderIntoDocument(
-
- );
-
- assert.equal(instance.refs.overlay.getOverlayDOMNode(), null);
- });
-
- it('Should render only an overlay', function() {
- let OnlyOverlay = React.createClass({
- mixins: [OverlayMixin],
-
- render() {
- return null;
- },
-
- renderOverlay() {
- return this.props.overlay;
- }
- });
-
- let overlayInstance = ReactTestUtils.renderIntoDocument(
- } />
- );
-
- assert.equal(overlayInstance.getOverlayDOMNode().nodeName, 'DIV');
- });
-});
diff --git a/test/helpers.js b/test/helpers.js
index 74960b4db4..97fb38d589 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -1,5 +1,30 @@
+import React from 'react';
+import { cloneElement } from 'react';
+
export function shouldWarn(about) {
console.warn.called.should.be.true;
console.warn.calledWithMatch(about).should.be.true;
console.warn.reset();
}
+
+/**
+ * Helper for rendering and updating props for plain class Components
+ * since `setProps` is deprecated.
+ * @param {ReactElement} element Root element to render
+ * @param {HTMLElement?} mountPoint Optional mount node, when empty it uses an unattached div like `renderIntoDocument()`
+ * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance wiht updated props
+ */
+export function render(element, mountPoint){
+ let mount = mountPoint || document.createElement('div');
+ let instance = React.render(element, mount);
+
+ if (!instance.renderWithProps) {
+ instance.renderWithProps = function(newProps) {
+
+ return render(
+ cloneElement(element, newProps), mount);
+ };
+ }
+
+ return instance;
+}
diff --git a/test/server/ModalSpec.js b/test/server/ModalSpec.js
index e78832c956..fae565c4c8 100644
--- a/test/server/ModalSpec.js
+++ b/test/server/ModalSpec.js
@@ -8,7 +8,7 @@ describe('Modal', () => {
assert.doesNotThrow(function renderOnServerSide() {
return React.renderToString(
-
+ Message
);
From 3a0b4da0ca7c4fd7fa409a168173875c6ada8eb4 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:12:17 -0400
Subject: [PATCH 023/128] Add transition Component
---
src/Transition.js | 286 +++++++++++++++++++++++++++++++++++++++++
test/TransitionSpec.js | 231 +++++++++++++++++++++++++++++++++
2 files changed, 517 insertions(+)
create mode 100644 src/Transition.js
create mode 100644 test/TransitionSpec.js
diff --git a/src/Transition.js b/src/Transition.js
new file mode 100644
index 0000000000..ce8eb200cf
--- /dev/null
+++ b/src/Transition.js
@@ -0,0 +1,286 @@
+'use strict';
+import React from 'react';
+import TransitionEvents from './utils/TransitionEvents';
+import classnames from 'classnames';
+
+function omit(obj, keys) {
+ let included = Object.keys(obj).filter( k => keys.indexOf(k) === -1);
+ let newObj = {};
+
+ included.forEach( key => newObj[key] = obj[key] );
+ return newObj;
+}
+
+function ensureTransitionEnd(node, handler, duration){
+ let fired = false;
+ let done = e => {
+ if (!fired) {
+ fired = true;
+ handler(e);
+ }
+ };
+
+ if ( node ) {
+ TransitionEvents.addEndEventListener(node, done);
+ setTimeout(done, duration);
+ } else {
+ setTimeout(done, 0);
+ }
+}
+
+// reading a dimension prop will cause the browser to recalculate,
+// which will let our animations work
+let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+
+class Transition extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+
+ this.state = {
+ in: !props.in,
+ transitioning: false
+ };
+
+ this.needsTransition = true;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.in !== this.props.in) {
+ this.needsTransition = true;
+ }
+ }
+
+ componentDidUpdate() {
+ this.processChild();
+ }
+
+ componentWillMount() {
+ this._mounted = true;
+
+ if (!this.props.transitionAppear) {
+ this.needsTransition = false;
+ this.setState({ in: this.props.in });
+ }
+ }
+
+ componentWillUnmount(){
+ this._mounted = false;
+ }
+
+ componentDidMount() {
+ if (this.props.transitionAppear) {
+ this.processChild();
+ }
+ }
+
+ processChild(){
+ let needsTransition = this.needsTransition;
+ let enter = this.props.in;
+
+ if (needsTransition) {
+ this.needsTransition = false;
+ this[enter ? 'performEnter' : 'performLeave']();
+ }
+ }
+
+ performEnter() {
+ let maybeNode = React.findDOMNode(this);
+
+ let enter = node => {
+ node = this.props.transitioningNode(node) || node;
+
+ this.props.onEnter(node);
+
+ this.safeSetState({ in: true, transitioning: true, needInitialRender: false }, ()=> {
+
+ this.props.onEntering(node);
+
+ ensureTransitionEnd(node, () => {
+ if ( this.state.in ){
+ this.safeSetState({
+ transitioning: false
+ }, () => this.props.onEntered(node));
+ }
+
+ }, this.props.duration);
+ });
+ };
+
+ if (maybeNode) {
+ enter(maybeNode);
+ }
+ else if (this.props.unmountOnExit) {
+ this._ensureNode(enter);
+ }
+ }
+
+ performLeave() {
+ let node = React.findDOMNode(this);
+
+ node = this.props.transitioningNode(node) || node;
+
+ this.props.onExit(node);
+
+ this.setState({ in: false, transitioning: true }, () => {
+ this.props.onExiting(node);
+
+ ensureTransitionEnd(node, () => {
+ if ( !this.state.in ){
+ this.safeSetState({ transitioning: false }, ()=> this.props.onExited(node));
+ }
+ }, this.props.duration);
+ });
+ }
+
+ _ensureNode(callback) {
+
+ this.setState({ needInitialRender: true }, ()=> {
+ let node = React.findDOMNode(this);
+
+ triggerBrowserReflow(node);
+
+ callback(node);
+ });
+ }
+
+ safeSetState(newState, cb){
+ if (this._mounted) {
+ this.setState(newState, cb);
+ }
+ }
+
+ render() {
+ let childProps = omit(this.props, Object.keys(Transition.propTypes).concat('children'));
+
+ let child = this.props.children;
+ let starting = this.state.needInitialRender;
+ let out = !this.state.in && !this.state.transitioning;
+
+ if ( !child || (this.props.unmountOnExit && out && !starting) ){
+ return null;
+ }
+
+ let classes = '';
+
+ // for whatever reason classnames() doesn't actually work here,
+ // maybe because they aren't always single classes?
+ if (this.state.in && !this.state.transitioning) {
+ classes = this.props.enteredClassName;
+ }
+
+ else if (this.state.in && this.state.transitioning) {
+ classes = this.props.enteringClassName;
+ }
+
+ else if (!this.state.in && !this.state.transitioning) {
+ classes = this.props.exitedClassName;
+ }
+
+ else if (!this.state.in && this.state.transitioning) {
+ classes = this.props.exitingClassName;
+ }
+
+ return React.cloneElement(child, {
+ ...childProps,
+ className: classnames(
+ child.props.className
+ , this.props.className
+ , classes)
+ });
+ }
+}
+
+Transition.propTypes = {
+ /**
+ * Triggers the Enter or Exit animation
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether transitions should run when the Transition component mounts.
+ */
+ transitionAppear: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * A css class or classes applied once the Component has exited.
+ */
+ exitedClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied while the Component is exiting.
+ */
+ exitingClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied once the Component has entered.
+ */
+ enteredClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied while the Component is entering.
+ */
+ enteringClassName: React.PropTypes.string,
+
+ /**
+ * A function that returns the DOM node to animate. This Node will have the transition classes applied to it.
+ * When left out, the Component will use its immediate child.
+ *
+ * @private
+ */
+ transitioningNode: React.PropTypes.func,
+
+ /**
+ * A callback fired just before the "entering" classes are applied
+ */
+ onEnter: React.PropTypes.func,
+ /**
+ * A callback fired just after the "entering" classes are applied
+ */
+ onEntering: React.PropTypes.func,
+ /**
+ * A callback fired after "enter" classes are applied
+ */
+ onEntered: React.PropTypes.func,
+ /**
+ * A callback fired after "exiting" classes are applied
+ */
+ onExit: React.PropTypes.func,
+ /**
+ * A callback fired after "exiting" classes are applied
+ */
+ onExiting: React.PropTypes.func,
+ /**
+ * A callback fired after "exit" classes are applied
+ */
+ onExited: React.PropTypes.func
+};
+
+// name the function so it is clearer in the documentation
+const noop = ()=>{};
+
+Transition.defaultProps = {
+ in: false,
+ duration: 300,
+ unmountOnExit: false,
+ transitionAppear: false,
+ transitioningNode: noop,
+
+ onEnter: noop,
+ onEntering: noop,
+ onEntered: noop,
+
+ onExit: noop,
+ onExiting: noop,
+ onExited: noop
+};
+
+export default Transition;
diff --git a/test/TransitionSpec.js b/test/TransitionSpec.js
new file mode 100644
index 0000000000..7945ba8ceb
--- /dev/null
+++ b/test/TransitionSpec.js
@@ -0,0 +1,231 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import { render } from './helpers';
+import Transition from '../src/Transition';
+//import classNames from 'classnames';
+
+describe('Transition', function () {
+
+
+ it('should not transition on mount', function(){
+ let instance = render(
+ { throw new Error('should not Enter'); }}>
+
+
+ );
+
+ instance.state.in.should.equal(true);
+ assert.ok(!instance.state.transitioning);
+ });
+
+ it('should transition on mount with transitionAppear', done =>{
+ let instance = ReactTestUtils.renderIntoDocument(
+ done()}
+ >
+
+
+ );
+
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(true);
+ });
+
+ describe('entering', ()=> {
+ let instance;
+
+ beforeEach(function(){
+ instance = render(
+
+
+
+ );
+ });
+
+ it('should fire callbacks', done => {
+ let onEnter = sinon.spy();
+ let onEntering = sinon.spy();
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter,
+
+ onEntering,
+
+ onEntered(){
+ assert.ok(onEnter.calledOnce);
+ assert.ok(onEntering.calledOnce);
+ assert.ok(onEnter.calledBefore(onEntering));
+ done();
+ }
+ });
+ });
+
+ it('should move to each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter(){
+ count++;
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(false);
+ },
+
+ onEntering(){
+ count++;
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(true);
+ },
+
+ onEntered(){
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(false);
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ it('should apply classes at each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter(node){
+ count++;
+ assert.equal(node.className, '');
+ },
+
+ onEntering(node){
+ count++;
+ assert.equal(node.className, 'test-entering');
+ },
+
+ onEntered(node){
+ assert.equal(node.className, 'test-enter');
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ });
+
+
+ describe('exiting', ()=> {
+ let instance;
+
+ beforeEach(function(){
+ instance = render(
+
+
+
+ );
+ });
+
+ it('should fire callbacks', done => {
+ let onExit = sinon.spy();
+ let onExiting = sinon.spy();
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit,
+
+ onExiting,
+
+ onExited(){
+ assert.ok(onExit.calledOnce);
+ assert.ok(onExiting.calledOnce);
+ assert.ok(onExit.calledBefore(onExiting));
+ done();
+ }
+ });
+ });
+
+ it('should move to each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit(){
+ count++;
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(false);
+ },
+
+ onExiting(){
+ count++;
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(true);
+ },
+
+ onExited(){
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(false);
+ //assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ it('should apply classes at each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit(node){
+ count++;
+ assert.equal(node.className, '');
+ },
+
+ onExiting(node){
+ count++;
+ assert.equal(node.className, 'test-exiting');
+ },
+
+ onExited(node){
+ assert.equal(node.className, 'test-exit');
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ });
+
+});
From 0503507dd26d5a08f0a79339b67868e5f6b32c7e Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:14:06 -0400
Subject: [PATCH 024/128] [added] Collapse Component, replaces CollapsibleMixin
---
docs/examples/Collapse.js | 28 +++++
src/Collapse.js | 200 +++++++++++++++++++++++++++++++++++
src/CollapsibleNav.js | 54 +++++-----
src/Nav.js | 30 ++----
src/Panel.js | 49 +++++----
test/CollapseSpec.js | 216 ++++++++++++++++++++++++++++++++++++++
6 files changed, 513 insertions(+), 64 deletions(-)
create mode 100644 docs/examples/Collapse.js
create mode 100644 src/Collapse.js
create mode 100644 test/CollapseSpec.js
diff --git a/docs/examples/Collapse.js b/docs/examples/Collapse.js
new file mode 100644
index 0000000000..c30069d8d0
--- /dev/null
+++ b/docs/examples/Collapse.js
@@ -0,0 +1,28 @@
+class Example extends React.Component {
+ constructor(...args){
+ super(...args);
+
+ this.state = {};
+ }
+
+ render(){
+
+ return (
+
+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
+ Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
+
+
+
+
+ );
+ }
+}
+
+React.render(, mountNode);
diff --git a/src/Collapse.js b/src/Collapse.js
new file mode 100644
index 0000000000..0b14eec0b4
--- /dev/null
+++ b/src/Collapse.js
@@ -0,0 +1,200 @@
+/*eslint-disable react/prop-types */
+'use strict';
+import React from 'react';
+import Transition from './Transition';
+import domUtils from './utils/domUtils';
+import createChainedFunction from './utils/createChainedFunction';
+
+let capitalize = str => str[0].toUpperCase() + str.substr(1);
+
+// reading a dimension prop will cause the browser to recalculate,
+// which will let our animations work
+let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+
+const MARGINS = {
+ height: ['marginTop', 'marginBottom'],
+ width: ['marginLeft', 'marginRight']
+};
+
+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)
+ );
+}
+
+class Collapse extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+
+ this.onEnterListener = this.handleEnter.bind(this);
+ this.onEnteringListener = this.handleEntering.bind(this);
+ this.onEnteredListener = this.handleEntered.bind(this);
+ this.onExitListener = this.handleExit.bind(this);
+ this.onExitingListener = this.handleExiting.bind(this);
+ }
+
+ render() {
+ let enter = createChainedFunction(this.onEnterListener, this.props.onEnter);
+ let entering = createChainedFunction(this.onEnteringListener, this.props.onEntering);
+ let entered = createChainedFunction(this.onEnteredListener, this.props.onEntered);
+ let exit = createChainedFunction(this.onExitListener, this.props.onExit);
+ let exiting = createChainedFunction(this.onExitingListener, this.props.onExiting);
+
+ return (
+
+ { this.props.children }
+
+ );
+ }
+
+ /* -- Expanding -- */
+ handleEnter(elem){
+ let dimension = this._dimension();
+ elem.style[dimension] = '0';
+ }
+
+ handleEntering(elem){
+ let dimension = this._dimension();
+
+ elem.style[dimension] = this._getScrollDimensionValue(elem, dimension);
+ }
+
+ handleEntered(elem){
+ let dimension = this._dimension();
+ elem.style[dimension] = null;
+ }
+
+ /* -- Collapsing -- */
+ handleExit(elem){
+ let dimension = this._dimension();
+
+ elem.style[dimension] = this.props.getDimensionValue(dimension, elem) + 'px';
+ }
+
+ handleExiting(elem){
+ let dimension = this._dimension();
+
+ triggerBrowserReflow(elem);
+ elem.style[dimension] = '0';
+ }
+
+ _dimension(){
+ return typeof this.props.dimension === 'function'
+ ? this.props.dimension()
+ : this.props.dimension;
+ }
+
+ //for testing
+ _getTransitionInstance(){
+ return this.refs.transition;
+ }
+
+ _getScrollDimensionValue(elem, dimension){
+ return elem[`scroll${capitalize(dimension)}`] + 'px';
+ }
+}
+
+Collapse.propTypes = {
+ /**
+ * Collapse the Component in or out.
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * Specifies the dimension used when collapsing.
+ *
+ * _Note: Bootstrap only partially supports this!
+ * You will need to supply your own css animation for the `.width` css class._
+ */
+ dimension: React.PropTypes.oneOfType([
+ React.PropTypes.oneOf(['height', 'width']),
+ React.PropTypes.func
+ ]),
+
+ /**
+ * A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
+ * Collapse component should animation in its specified dimension.
+ *
+ * `getDimensionValue` is called with the current dimension prop value and the DOM node.
+ */
+ getDimensionValue: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to expand.
+ */
+ onEnter: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component starts to expand.
+ */
+ onEntering: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has expanded.
+ */
+ onEntered: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to collapse.
+ */
+ onExit: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component starts to collapse.
+ */
+ onExiting: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has collapsed.
+ */
+ onExited: React.PropTypes.func,
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether the component should collapse or expand when it mounts.
+ */
+ transitionAppear: React.PropTypes.bool
+};
+
+Collapse.defaultProps = {
+ in: false,
+ duration: 300,
+ dimension: 'height',
+ transitionAppear: false,
+ unmountOnExit: false,
+ getDimensionValue
+};
+
+export default Collapse;
+
diff --git a/src/CollapsibleNav.js b/src/CollapsibleNav.js
index 427d0b30f3..027f7cb29a 100644
--- a/src/CollapsibleNav.js
+++ b/src/CollapsibleNav.js
@@ -1,14 +1,13 @@
import React, { cloneElement } from 'react';
import BootstrapMixin from './BootstrapMixin';
-import CollapsibleMixin from './CollapsibleMixin';
+import Collapse from './Collapse';
import classNames from 'classnames';
-import domUtils from './utils/domUtils';
import ValidComponentChildren from './utils/ValidComponentChildren';
import createChainedFunction from './utils/createChainedFunction';
const CollapsibleNav = React.createClass({
- mixins: [BootstrapMixin, CollapsibleMixin],
+ mixins: [BootstrapMixin],
propTypes: {
onSelect: React.PropTypes.func,
@@ -19,41 +18,48 @@ const CollapsibleNav = React.createClass({
eventKey: React.PropTypes.any
},
- getCollapsibleDOMNode() {
- return React.findDOMNode(this);
- },
- getCollapsibleDimensionValue() {
- let height = 0;
- let nodes = this.refs;
- for (let key in nodes) {
- if (nodes.hasOwnProperty(key)) {
+ // getCollapsibleDimensionValue() {
+ // let height = 0;
+ // let nodes = this.refs;
+ // for (let key in nodes) {
+ // if (nodes.hasOwnProperty(key)) {
- let n = React.findDOMNode(nodes[key]);
- let h = n.offsetHeight;
- let computedStyles = domUtils.getComputedStyles(n);
+ // let n = React.findDOMNode(nodes[key]);
+ // let h = n.offsetHeight;
+ // let computedStyles = domUtils.getComputedStyles(n);
- height += (h +
- parseInt(computedStyles.marginTop, 10) +
- parseInt(computedStyles.marginBottom, 10)
- );
- }
- }
- return height;
- },
+ // height += (h +
+ // parseInt(computedStyles.marginTop, 10) +
+ // parseInt(computedStyles.marginBottom, 10)
+ // );
+ // }
+ // }
+ // return height;
+ // },
render() {
/*
* this.props.collapsible is set in NavBar when an eventKey is supplied.
*/
- const classes = this.props.collapsible ? this.getCollapsibleClassSet('navbar-collapse') : null;
+ const classes = this.props.collapsible ? 'navbar-collapse' : null;
const renderChildren = this.props.collapsible ? this.renderCollapsibleNavChildren : this.renderChildren;
- return (
+ let nav = (
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
+ Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
+
+
+
+
+ );
+ }
+}
+
+React.render(, mountNode);
diff --git a/src/Fade.js b/src/Fade.js
new file mode 100644
index 0000000000..b91cb32b51
--- /dev/null
+++ b/src/Fade.js
@@ -0,0 +1,90 @@
+'use strict';
+import React from 'react';
+import Transition from './Transition';
+
+class Fade extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+ }
+
+ render() {
+ return (
+
+ { this.props.children }
+
+ );
+ }
+}
+
+Fade.propTypes = {
+ /**
+ * Fade the Component in or out.
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * A Callback fired before the component starts to fade in.
+ */
+ onEnter: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component has started to faded in.
+ */
+ onEntering: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has faded in.
+ */
+ onEntered: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to fade out.
+ */
+ onExit: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component has started to faded out.
+ */
+ onExiting: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has faded out.
+ */
+ onExited: React.PropTypes.func,
+
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether the component should fade in or out when it mounts.
+ */
+ transitionAppear: React.PropTypes.bool
+
+};
+
+Fade.defaultProps = {
+ in: false,
+ duration: 300,
+ dimension: 'height',
+ transitionAppear: false,
+ unmountOnExit: false
+};
+
+export default Fade;
+
diff --git a/src/Modal.js b/src/Modal.js
index 3816318c51..55df65f433 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -4,11 +4,11 @@ import React, { cloneElement } from 'react';
import classNames from 'classnames';
import createChainedFunction from './utils/createChainedFunction';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import domUtils from './utils/domUtils';
import EventListener from './utils/EventListener';
import Portal from './Portal';
+import Fade from './Fade';
import Body from './ModalBody';
import Header from './ModalHeader';
@@ -90,12 +90,17 @@ function getScrollbarSize(){
document.body.removeChild(scrollDiv);
scrollDiv = null;
+ return scrollbarSize;
}
const ModalMarkup = React.createClass({
+<<<<<<< HEAD
mixins: [ BootstrapMixin, FadeMixin ],
+=======
+ mixins: [ BootstrapMixin ],
+>>>>>>> [added] Fade Component, replaces FadeMixin
propTypes: {
@@ -166,8 +171,7 @@ const ModalMarkup = React.createClass({
let classes = {
modal: true,
- fade: this.props.animation,
- 'in': !this.props.animation
+ in: this.props.show && !this.props.animation
};
let modal = (
@@ -206,18 +210,22 @@ const ModalMarkup = React.createClass({
},
renderBackdrop(modal) {
- let classes = {
- 'modal-backdrop': true,
- fade: this.props.animation,
- 'in': !this.props.animation
- };
-
- let onClick = this.props.backdrop === true ?
- this.handleBackdropClick : null;
+ let { animation } = this.props;
+ let duration = Modal.BACKDROP_TRANSITION_DURATION; //eslint-disable-line no-use-before-define
+
+ let backdrop = (
+
+ );
return (
);
@@ -381,16 +389,40 @@ const Modal = React.createClass({
...ModalMarkup.propTypes
},
+ getDefaultProps(){
+ return {
+ show: false,
+ animation: true
+ };
+ },
+
render() {
- let { show, ...props } = this.props;
+ let { children, ...props } = this.props;
+
+ let show = !!props.show;
let modal = (
- {this.props.children}
+
+ { children }
+
);
return (
-
- { show && modal }
+
+ { props.animation
+ ? (
+
+ { modal }
+
+ )
+ : show && modal
+ }
+
);
}
@@ -401,4 +433,7 @@ Modal.Header = Header;
Modal.Title = Title;
Modal.Footer = Footer;
+Modal.TRANSITION_DURATION = 300;
+Modal.BACKDROP_TRANSITION_DURATION = 150;
+
export default Modal;
diff --git a/src/Overlay.js b/src/Overlay.js
index d425fe91a4..a5b27d923f 100644
--- a/src/Overlay.js
+++ b/src/Overlay.js
@@ -1,13 +1,26 @@
/*eslint-disable object-shorthand, react/prop-types */
-import React from 'react';
+import React, { cloneElement } from 'react';
import Portal from './Portal';
import Position from './Position';
import RootCloseWrapper from './RootCloseWrapper';
+import CustomPropTypes from './utils/CustomPropTypes';
+import Fade from './Fade';
+import classNames from 'classnames';
+
class Overlay extends React.Component {
constructor(props, context){
super(props, context);
+
+ this.state = { exited: false };
+ this.onHiddenListener = this.handleHidden.bind(this);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.show){
+ this.setState({ exited: false });
+ }
}
render(){
@@ -17,30 +30,60 @@ class Overlay extends React.Component {
, target
, placement
, rootClose
+ , children
+ , animation: Transition
, ...props } = this.props;
- let positionedChild = (
-
- { this.props.children }
-
- );
+ let child = null;
- if (rootClose) {
- positionedChild = (
-
- { positionedChild }
-
+ if ( Transition === true ){
+ Transition = Fade;
+ }
+
+ if (props.show || (Transition && !this.state.exited)) {
+
+ child = children;
+
+ // Position the child before the animation to avoid `null` DOM nodes
+ child = (
+
+ { child }
+
);
+
+ child = Transition
+ ? (
+
+ { child }
+
+ )
+ : cloneElement(child, { className: classNames('in', child.className) });
+
+
+ if (rootClose) {
+ child = (
+
+ { child }
+
+ );
+ }
}
return (
-
- { props.show &&
- positionedChild
- }
+
+ { child }
);
}
+
+ handleHidden(){
+ this.setState({ exited: true });
+ }
}
Overlay.propTypes = {
@@ -57,7 +100,19 @@ Overlay.propTypes = {
/**
* A Callback fired by the Overlay when it wishes to be hidden.
*/
- onHide: React.PropTypes.func
+ onHide: React.PropTypes.func,
+
+ /**
+ * Use animation
+ */
+ animation: React.PropTypes.oneOfType([
+ React.PropTypes.bool,
+ CustomPropTypes.elementType
+ ])
+};
+
+Overlay.defaultProps = {
+ animation: Fade
};
export default Overlay;
diff --git a/src/Popover.js b/src/Popover.js
index e24b0c51a6..1133703238 100644
--- a/src/Popover.js
+++ b/src/Popover.js
@@ -2,12 +2,11 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import CustomPropTypes from './utils/CustomPropTypes';
const Popover = React.createClass({
- mixins: [BootstrapMixin, FadeMixin],
+ mixins: [ BootstrapMixin ],
propTypes: {
/**
@@ -42,15 +41,11 @@ const Popover = React.createClass({
arrowOffsetTop: React.PropTypes.oneOfType([
React.PropTypes.number, React.PropTypes.string
]),
+
/**
* Title text
*/
- title: React.PropTypes.node,
- /**
- * Specify whether the Popover should be use show and hide animations.
- */
- animation: React.PropTypes.bool
-
+ title: React.PropTypes.node
},
@@ -64,10 +59,7 @@ const Popover = React.createClass({
render() {
const classes = {
'popover': true,
- [this.props.placement]: true,
- // in class will be added by the FadeMixin when the animation property is true
- 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null),
- 'fade': this.props.animation
+ [this.props.placement]: true
};
const style = {
diff --git a/src/Position.js b/src/Position.js
index b92c256965..36f9d817ba 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -31,13 +31,13 @@ class Position extends React.Component {
}
render() {
- let { placement, children } = this.props;
+ let { children, ...props } = this.props;
let { positionLeft, positionTop, ...arrows } = this.props.target ? this.state : {};
return cloneElement(
React.Children.only(children), {
+ ...props,
...arrows,
- placement,
positionTop,
positionLeft,
style: {
@@ -61,13 +61,18 @@ class Position extends React.Component {
return;
}
+ let overlay = React.findDOMNode(this);
let target = React.findDOMNode(this.props.target(this.props));
let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
+ // if ( !overlay || !target || !container ){
+ // return;
+ // }
+
this.setState(
calcOverlayPosition(
this.props.placement
- , React.findDOMNode(this)
+ , overlay
, target
, container
, this.props.containerPadding));
diff --git a/src/Tooltip.js b/src/Tooltip.js
index 640267829e..e21c57c845 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -2,11 +2,10 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import CustomPropTypes from './utils/CustomPropTypes';
const Tooltip = React.createClass({
- mixins: [BootstrapMixin, FadeMixin],
+ mixins: [BootstrapMixin],
propTypes: {
/**
@@ -44,11 +43,7 @@ const Tooltip = React.createClass({
/**
* Title text
*/
- title: React.PropTypes.node,
- /**
- * Specify whether the Tooltip should be use show and hide animations.
- */
- animation: React.PropTypes.bool
+ title: React.PropTypes.node
},
getDefaultProps() {
@@ -61,10 +56,7 @@ const Tooltip = React.createClass({
render() {
const classes = {
'tooltip': true,
- [this.props.placement]: true,
- // in class will be added by the FadeMixin when the animation property is true
- 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null),
- 'fade': this.props.animation
+ [this.props.placement]: true
};
const style = {
diff --git a/test/FadeSpec.js b/test/FadeSpec.js
new file mode 100644
index 0000000000..1aa710fcd6
--- /dev/null
+++ b/test/FadeSpec.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import Fade from '../src/Fade';
+//import classNames from 'classnames';
+
+describe('Fade', function () {
+
+ let Component, instance;
+
+ beforeEach(function(){
+
+ Component = React.createClass({
+ render(){
+ let { children, ...props } = this.props;
+
+ return (
+ this.fade = r}
+ {...props}
+ >
+
+ {children}
+
+
+ );
+ }
+ });
+ });
+
+ it('Should default to hidden', function () {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ assert.ok(
+ instance.fade.props.in === false);
+ });
+
+ it('Should always have the "fade" class', () => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ assert.ok(
+ instance.fade.props.in === false);
+
+ assert.equal(
+ React.findDOMNode(instance).className, 'fade');
+
+ });
+
+ it('Should add "in" class when entering', done => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ function onEntering(){
+ assert.equal(React.findDOMNode(instance).className, 'fade in');
+ done();
+ }
+
+ assert.ok(
+ instance.fade.props.in === false);
+
+ instance.setProps({ in: true, onEntering });
+ });
+
+ it('Should remove "in" class when exiting', done => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ function onExiting(){
+ assert.equal(React.findDOMNode(instance).className, 'fade');
+ done();
+ }
+
+ assert.equal(
+ React.findDOMNode(instance).className, 'fade in');
+
+ instance.setProps({ in: false, onExiting });
+ });
+});
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 6065c0bbc3..d45636cd1d 100644
--- a/test/ModalSpec.js
+++ b/test/ModalSpec.js
@@ -3,6 +3,7 @@ import ReactTestUtils from 'react/lib/ReactTestUtils';
import Modal from '../src/Modal';
import { render } from './helpers';
+
describe('Modal', function () {
let mountPoint;
@@ -172,10 +173,8 @@ describe('Modal', function () {
document.activeElement.should.equal(focusableContainer);
});
- it('Should not focus on the Modal when autoFocus is false', function () {
-
- document.activeElement.should.equal(focusableContainer);
+ it('Should not focus on the Modal when autoFocus is false', function () {
render(
{}} animation={false}>
Message
diff --git a/test/PopoverSpec.js b/test/PopoverSpec.js
index 45b6f00757..30509376e4 100644
--- a/test/PopoverSpec.js
+++ b/test/PopoverSpec.js
@@ -11,16 +11,16 @@ describe('Popover', function () {
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-title'));
assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-content'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade'));
+
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
});
- it('Should not have the fade class if animation is false', function () {
- let instance = ReactTestUtils.renderIntoDocument(
-
- Popover Content
-
- );
- assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- });
+ // it('Should not have the fade class if animation is false', function () {
+ // let instance = ReactTestUtils.renderIntoDocument(
+ //
+ // Popover Content
+ //
+ // );
+ // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
+ // });
});
diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js
index a07bb06073..a21c6317bf 100644
--- a/test/TooltipSpec.js
+++ b/test/TooltipSpec.js
@@ -10,15 +10,15 @@ describe('Tooltip', function () {
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade'));
- });
- it('Should not have the fade class if animation is false', function () {
- let instance = ReactTestUtils.renderIntoDocument(
-
- Tooltip Content
-
- );
- assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
});
+
+ // it('Should not have the fade class if animation is false', function () {
+ // let instance = ReactTestUtils.renderIntoDocument(
+ //
+ // Tooltip Content
+ //
+ // );
+ // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
+ // });
});
From 411050ba8b1e7780b61a25237e9a43cfc4243dce Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:15:18 -0400
Subject: [PATCH 026/128] add docs and exports
---
docs/examples/.eslintrc | 4 +++-
docs/src/ComponentsPage.js | 21 +++++++++++++++++++--
docs/src/ReactPlayground.js | 11 +++++++++++
docs/src/Samples.js | 3 +++
src/Collapse.js | 2 +-
src/Input.js | 2 +-
src/index.js | 3 +++
7 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/docs/examples/.eslintrc b/docs/examples/.eslintrc
index 9dbc49c623..00c22821a7 100644
--- a/docs/examples/.eslintrc
+++ b/docs/examples/.eslintrc
@@ -52,6 +52,8 @@
"TabPane",
"Tooltip",
"Well",
- "Thumbnail"
+ "Thumbnail",
+ "Collapse",
+ "Fade"
}
}
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 8e656f7048..52f422f6a7 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -56,6 +56,7 @@ const ComponentsPage = React.createClass({
+
{/* Buttons */}
Buttons Button
@@ -843,7 +844,7 @@ const ComponentsPage = React.createClass({
equivalent to jQuery's .appendTo(), which is helpful for components that need to be appended to a DOM node other than
the component's direct parent. The Modal, and Overlay components use the Portal component internally.
-
Props
+
Props
@@ -852,9 +853,25 @@ const ComponentsPage = React.createClass({
A Component that absolutely positions its child to a target component or DOM node. Useful for creating custom
popups or tooltips. Used by the Overlay Components.
-
Props
+
Props
+
+
Transitions
+
+
Collapse
+
Add a collapse toggle animation to an element or component.
+
+
+
Props
+
+
+
Fade
+
Add a fade animation to a child element or component.
+
+
+
Props
+
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 4ab4cd230b..4e60798965 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -6,13 +6,19 @@ import * as modBadge from '../../src/Badge';
import * as modButton from '../../src/Button';
import * as modButtonGroup from '../../src/ButtonGroup';
import * as modButtonInput from '../../src/ButtonInput';
+
import * as modButtonToolbar from '../../src/ButtonToolbar';
+import * as modCollapse from '../../src/Collapse';
+
import * as modCollapsibleNav from '../../src/CollapsibleNav';
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
import * as modCarousel from '../../src/Carousel';
import * as modCarouselItem from '../../src/CarouselItem';
import * as modCol from '../../src/Col';
import * as modDropdownButton from '../../src/DropdownButton';
+
+import * as modFade from '../../src/Fade';
+
import * as modFormControls from '../../src/FormControls';
import * as modGlyphicon from '../../src/Glyphicon';
import * as modGrid from '../../src/Grid';
@@ -57,8 +63,13 @@ import CodeExample from './CodeExample';
const classNames = modClassNames.default;
+
/* eslint-disable */
+
const Portal = modPortal.default;
+const Collapse = modCollapse.default;
+const Fade = modFade.default;
+
const React = modReact.default;
const Accordion = modAccordion.default;
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index e6401dc08f..d8a4f8caf1 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -1,6 +1,9 @@
/* eslint no-path-concat: 0, no-var: 0 */
export default {
+ Collapse: require('fs').readFileSync(__dirname + '/../examples/Collapse.js', 'utf8'),
+ Fade: require('fs').readFileSync(__dirname + '/../examples/Fade.js', 'utf8'),
+
ButtonTypes: require('fs').readFileSync(__dirname + '/../examples/ButtonTypes.js', 'utf8'),
ButtonSizes: require('fs').readFileSync(__dirname + '/../examples/ButtonSizes.js', 'utf8'),
ButtonBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonBlock.js', 'utf8'),
diff --git a/src/Collapse.js b/src/Collapse.js
index 0b14eec0b4..f9799b8b60 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -130,7 +130,7 @@ Collapse.propTypes = {
/**
* Specifies the dimension used when collapsing.
*
- * _Note: Bootstrap only partially supports this!
+ * _Note: Bootstrap only partially supports 'width'!
* You will need to supply your own css animation for the `.width` css class._
*/
dimension: React.PropTypes.oneOfType([
diff --git a/src/Input.js b/src/Input.js
index ec1917da8e..2e81cac1bc 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -5,7 +5,7 @@ import deprecationWarning from './utils/deprecationWarning';
class Input extends InputBase {
render() {
- if (this.props.type === 'static') { //eslint-disable-line react/prop-types
+ if (this.props.type === 'static') { // eslint-disable-line react/prop-types
deprecationWarning('Input type=static', 'StaticText');
return ;
}
diff --git a/src/index.js b/src/index.js
index 70802d2b28..ae6077a6f2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -63,5 +63,8 @@ export Well from './Well';
export Portal from './Portal';
export Position from './Position';
+export Collapse from './Collapse';
+export Fade from './Collapse';
+
export * as FormControls from './FormControls';
export * as utils from './utils';
From c3b41af621e9b4d4fbdc8a095b2d666420735534 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sun, 12 Jul 2015 16:46:04 -0400
Subject: [PATCH 027/128] fix typos, and remove comments
---
src/Collapse.js | 6 ++----
src/CollapsibleMixin.js | 10 ++++++++++
src/CollapsibleNav.js | 19 -------------------
src/Fade.js | 3 +--
src/FadeMixin.js | 10 ++++++++++
src/Modal.js | 4 ----
src/Position.js | 4 ----
src/Transition.js | 13 ++++++-------
test/CollapsibleMixinSpec.js | 6 ++++++
test/FadeMixinSpec.js | 7 +++++++
test/FadeSpec.js | 1 -
test/PopoverSpec.js | 8 --------
test/TooltipSpec.js | 8 --------
test/helpers.js | 2 +-
14 files changed, 43 insertions(+), 58 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index f9799b8b60..505c116085 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -1,5 +1,3 @@
-/*eslint-disable react/prop-types */
-'use strict';
import React from 'react';
import Transition from './Transition';
import domUtils from './utils/domUtils';
@@ -122,7 +120,7 @@ Collapse.propTypes = {
in: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
@@ -140,7 +138,7 @@ Collapse.propTypes = {
/**
* A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
- * Collapse component should animation in its specified dimension.
+ * Collapse component should animate in its specified dimension.
*
* `getDimensionValue` is called with the current dimension prop value and the DOM node.
*/
diff --git a/src/CollapsibleMixin.js b/src/CollapsibleMixin.js
index 31895e4041..c1abcbaada 100644
--- a/src/CollapsibleMixin.js
+++ b/src/CollapsibleMixin.js
@@ -1,5 +1,8 @@
import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
+import deprecationWarning from './utils/deprecationWarning';
+
+let warned = false;
const CollapsibleMixin = {
@@ -21,6 +24,13 @@ const CollapsibleMixin = {
};
},
+ componentWillMount(){
+ if ( !warned ){
+ deprecationWarning('CollapsibleMixin', 'Collapse Component');
+ warned = true;
+ }
+ },
+
componentWillUpdate(nextProps, nextState){
let willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded;
if (willExpanded === this.isExpanded()) {
diff --git a/src/CollapsibleNav.js b/src/CollapsibleNav.js
index 027f7cb29a..04a21db3ef 100644
--- a/src/CollapsibleNav.js
+++ b/src/CollapsibleNav.js
@@ -19,25 +19,6 @@ const CollapsibleNav = React.createClass({
},
- // getCollapsibleDimensionValue() {
- // let height = 0;
- // let nodes = this.refs;
- // for (let key in nodes) {
- // if (nodes.hasOwnProperty(key)) {
-
- // let n = React.findDOMNode(nodes[key]);
- // let h = n.offsetHeight;
- // let computedStyles = domUtils.getComputedStyles(n);
-
- // height += (h +
- // parseInt(computedStyles.marginTop, 10) +
- // parseInt(computedStyles.marginBottom, 10)
- // );
- // }
- // }
- // return height;
- // },
-
render() {
/*
* this.props.collapsible is set in NavBar when an eventKey is supplied.
diff --git a/src/Fade.js b/src/Fade.js
index b91cb32b51..523508fff5 100644
--- a/src/Fade.js
+++ b/src/Fade.js
@@ -1,4 +1,3 @@
-'use strict';
import React from 'react';
import Transition from './Transition';
@@ -30,7 +29,7 @@ Fade.propTypes = {
in: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
diff --git a/src/FadeMixin.js b/src/FadeMixin.js
index b5597d013d..b4dfe666f2 100644
--- a/src/FadeMixin.js
+++ b/src/FadeMixin.js
@@ -1,5 +1,6 @@
import React from 'react';
import domUtils from './utils/domUtils';
+import deprecationWarning from './utils/deprecationWarning';
// TODO: listen for onTransitionEnd to remove el
function getElementsAndSelf (root, classes){
@@ -16,7 +17,16 @@ function getElementsAndSelf (root, classes){
return els;
}
+let warned = false;
+
export default {
+ componentWillMount(){
+ if ( !warned ){
+ deprecationWarning('FadeMixin', 'Fade Component');
+ warned = true;
+ }
+ },
+
_fadeIn() {
let els;
diff --git a/src/Modal.js b/src/Modal.js
index 55df65f433..881b160e59 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -96,11 +96,7 @@ function getScrollbarSize(){
const ModalMarkup = React.createClass({
-<<<<<<< HEAD
- mixins: [ BootstrapMixin, FadeMixin ],
-=======
mixins: [ BootstrapMixin ],
->>>>>>> [added] Fade Component, replaces FadeMixin
propTypes: {
diff --git a/src/Position.js b/src/Position.js
index 36f9d817ba..c8c11b7904 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -65,10 +65,6 @@ class Position extends React.Component {
let target = React.findDOMNode(this.props.target(this.props));
let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
- // if ( !overlay || !target || !container ){
- // return;
- // }
-
this.setState(
calcOverlayPosition(
this.props.placement
diff --git a/src/Transition.js b/src/Transition.js
index ce8eb200cf..a49124aac7 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -1,4 +1,3 @@
-'use strict';
import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
import classnames from 'classnames';
@@ -163,8 +162,8 @@ class Transition extends React.Component {
let classes = '';
- // for whatever reason classnames() doesn't actually work here,
- // maybe because they aren't always single classes?
+ // using `classnames()` here causes a subtle bug,
+ // hence the verbose if/else if sequence.
if (this.state.in && !this.state.transitioning) {
classes = this.props.enteredClassName;
}
@@ -184,9 +183,9 @@ class Transition extends React.Component {
return React.cloneElement(child, {
...childProps,
className: classnames(
- child.props.className
- , this.props.className
- , classes)
+ child.props.className,
+ this.props.className,
+ classes)
});
}
}
@@ -208,7 +207,7 @@ Transition.propTypes = {
transitionAppear: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
diff --git a/test/CollapsibleMixinSpec.js b/test/CollapsibleMixinSpec.js
index 324b32ad44..c38527d2ae 100644
--- a/test/CollapsibleMixinSpec.js
+++ b/test/CollapsibleMixinSpec.js
@@ -32,6 +32,12 @@ describe('CollapsibleMixin', function () {
});
});
+ afterEach(()=> {
+ if (console.warn.calledWithMatch('CollapsibleMixin is deprecated')){
+ console.warn.reset();
+ }
+ });
+
describe('getInitialState', function(){
it('Should check defaultExpanded', function () {
instance = ReactTestUtils.renderIntoDocument(
diff --git a/test/FadeMixinSpec.js b/test/FadeMixinSpec.js
index bcd055b5c6..7e6086dd0f 100644
--- a/test/FadeMixinSpec.js
+++ b/test/FadeMixinSpec.js
@@ -19,6 +19,13 @@ describe('FadeMixin', function () {
});
});
+ afterEach(()=> {
+ if (console.warn.calledWithMatch('FadeMixin is deprecated')){
+ console.warn.reset();
+ }
+ });
+
+
it('Should add the in class to all elements', function (done) {
let instance = ReactTestUtils.renderIntoDocument();
diff --git a/test/FadeSpec.js b/test/FadeSpec.js
index 1aa710fcd6..0f85fdfd40 100644
--- a/test/FadeSpec.js
+++ b/test/FadeSpec.js
@@ -1,7 +1,6 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import Fade from '../src/Fade';
-//import classNames from 'classnames';
describe('Fade', function () {
diff --git a/test/PopoverSpec.js b/test/PopoverSpec.js
index 30509376e4..cca9014442 100644
--- a/test/PopoverSpec.js
+++ b/test/PopoverSpec.js
@@ -15,12 +15,4 @@ describe('Popover', function () {
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
});
- // it('Should not have the fade class if animation is false', function () {
- // let instance = ReactTestUtils.renderIntoDocument(
- //
- // Popover Content
- //
- // );
- // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- // });
});
diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js
index a21c6317bf..01b93bc42c 100644
--- a/test/TooltipSpec.js
+++ b/test/TooltipSpec.js
@@ -13,12 +13,4 @@ describe('Tooltip', function () {
});
- // it('Should not have the fade class if animation is false', function () {
- // let instance = ReactTestUtils.renderIntoDocument(
- //
- // Tooltip Content
- //
- // );
- // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- // });
});
diff --git a/test/helpers.js b/test/helpers.js
index 97fb38d589..9df659ecae 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -12,7 +12,7 @@ export function shouldWarn(about) {
* since `setProps` is deprecated.
* @param {ReactElement} element Root element to render
* @param {HTMLElement?} mountPoint Optional mount node, when empty it uses an unattached div like `renderIntoDocument()`
- * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance wiht updated props
+ * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance with updated props
*/
export function render(element, mountPoint){
let mount = mountPoint || document.createElement('div');
From 3e6523ac171442b419eacdb105063d358c2227b9 Mon Sep 17 00:00:00 2001
From: Adam Banko
Date: Fri, 10 Jul 2015 17:14:10 +0200
Subject: [PATCH 028/128] [added] ListGroup supports iterator as child
Fixes #935
---
src/ListGroup.js | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/src/ListGroup.js b/src/ListGroup.js
index 7301f050a8..30dca37c87 100644
--- a/src/ListGroup.js
+++ b/src/ListGroup.js
@@ -13,17 +13,11 @@ class ListGroup extends React.Component {
if (!this.props.children) {
return this.renderDiv(items);
- } else if (React.Children.count(this.props.children) === 1 && !Array.isArray(this.props.children)) {
- let child = this.props.children;
-
- childrenAnchors = this.isAnchor(child.props);
-
} else {
-
- childrenAnchors = Array.prototype.some.call(this.props.children, (child) => {
- return !Array.isArray(child) ? this.isAnchor(child.props) : Array.prototype.some.call(child, (subChild) => {
- return this.isAnchor(subChild.props);
- });
+ React.Children.forEach(this.props.children, (child) => {
+ if (this.isAnchor(child.props)) {
+ childrenAnchors = true;
+ }
});
From 1867a622e11fc546b52172f37b653568eca397ad Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Fri, 17 Jul 2015 14:06:24 +0300
Subject: [PATCH 029/128] Small fixes for 'tools'
Remove empty 'amd/lib' creation
Remove extraneous copying of 'LICENSE' for bower in building step
(release tool is doing it)
Simplify path to 'changelog' script
---
.gitignore | 2 +-
tools/amd/build.js | 8 ++------
tools/release-scripts/changelog.js | 2 +-
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/.gitignore b/.gitignore
index 580757f7be..95340486df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
*~
.DS_Store
-npm-debug.log
+npm-debug.log*
node_modules
amd/
!tools/amd/
diff --git a/tools/amd/build.js b/tools/amd/build.js
index 3f67d2c57f..21297f31ac 100644
--- a/tools/amd/build.js
+++ b/tools/amd/build.js
@@ -10,9 +10,6 @@ const bowerTemplate = path.join(__dirname, 'bower.json');
const bowerJson = path.join(bowerRoot, 'bower.json');
const readme = path.join(__dirname, 'README.md');
-const license = path.join(repoRoot, 'LICENSE');
-
-const libDestination = path.join(bowerRoot, 'lib');
function bowerConfig() {
return Promise.all([
@@ -29,11 +26,10 @@ export default function BuildBower() {
console.log('Building: '.cyan + 'bower module'.green);
return exec(`rimraf ${bowerRoot}`)
- .then(() => fsp.mkdirs(libDestination))
+ .then(() => fsp.mkdirs(bowerRoot))
.then(() => Promise.all([
bowerConfig(),
- copy(readme, bowerRoot),
- copy(license, bowerRoot)
+ copy(readme, bowerRoot)
]))
.then(() => console.log('Built: '.cyan + 'bower module'.green));
}
diff --git a/tools/release-scripts/changelog.js b/tools/release-scripts/changelog.js
index c31f53841a..c561fe86c9 100644
--- a/tools/release-scripts/changelog.js
+++ b/tools/release-scripts/changelog.js
@@ -26,7 +26,7 @@ export default (version) => {
}
return result
- .then(() => exec(`node_modules/.bin/changelog --title v${version} --out ${output}${additionalArgs}`))
+ .then(() => exec(`changelog --title v${version} --out ${output}${additionalArgs}`))
.then(() => safeExec(`git add ${changelog}`))
.then(() => {
if (removedAlphaChangelog || isPrerelease) {
From f4114c041beb5d3c4dec4ec5a3faed2147e64cb3 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Fri, 17 Jul 2015 15:06:26 +0300
Subject: [PATCH 030/128] Fix Modal live demo example path
---
docs/src/ComponentsPage.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 9b34f99e72..5274006ec7 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -281,7 +281,7 @@ const ComponentsPage = React.createClass({
Live demo
Use {''} in combination with other components to show or hide your Modal.
-
+
Contained Modal
You will need to add the following css to your project and ensure that your container has the modal-container class.
From 95f6c387b7181db631005f47ad18b35108a0d654 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Fri, 17 Jul 2015 15:44:08 +0300
Subject: [PATCH 031/128] Remove 'Collapsible Mixin' example from docs.
because it is deprecated
---
docs/src/ComponentsPage.js | 4 ----
1 file changed, 4 deletions(-)
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 9b34f99e72..c006f9f80c 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -247,10 +247,6 @@ const ComponentsPage = React.createClass({
<Accordion /> aliases <PanelGroup accordion />.
-
Collapsible Mixin
-
CollapsibleMixin can be used to create your own components with collapse functionality.
To have a mobile friendly Navbar, specify the property toggleNavKey on the Navbar with a value corresponding to an eventKey of one of his Nav children. This child will be the one collapsed.
-
By setting the property {React.DOM.code(null, 'defaultNavExpanded={true}')} the Navbar will start expanded by default.
+
By setting the property {React.DOM.code(null, 'defaultNavExpanded')} the Navbar will start expanded by default.
Scrollbar overflow
The height of the collapsible is slightly smaller than the real height. To hide the scroll bar, add the following css to your style files.
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 8dcf69d7b0..b966cd9dcd 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -328,7 +328,7 @@ const ReactPlayground = React.createClass({
if (this.props.renderCode) {
React.render(
- ,
+ ,
mountNode
);
} else {
diff --git a/src/Accordion.js b/src/Accordion.js
index 2aee587d9b..cc8e24555d 100644
--- a/src/Accordion.js
+++ b/src/Accordion.js
@@ -4,7 +4,7 @@ import PanelGroup from './PanelGroup';
const Accordion = React.createClass({
render() {
return (
-
+
{this.props.children}
);
From 73483e53031201e297f16349bf575e26523158ee Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Fri, 17 Jul 2015 16:33:21 +0300
Subject: [PATCH 034/128] Simplify 'aria-expanded' values setting for 'Panel'
http://www.w3.org/TR/wai-aria/states_and_properties#aria-expanded
It is not necessary to set them explicitly to strings.
Here https://github.com/react-bootstrap/react-bootstrap/blob/c3b41af621e9b4d4fbdc8a095b2d666420735534/src/Collapse.js#L51
we already do it like this:
`aria-expanded={this.props.in}`
Tests are asserting this
https://github.com/react-bootstrap/react-bootstrap/blob/ccc50e01917ba75e5a983ba068c1db4b790bb85d/test/PanelSpec.js#L196
https://github.com/react-bootstrap/react-bootstrap/blob/ccc50e01917ba75e5a983ba068c1db4b790bb85d/test/PanelSpec.js#L206
---
src/Panel.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Panel.js b/src/Panel.js
index d6dd1ae24a..7499aa8254 100644
--- a/src/Panel.js
+++ b/src/Panel.js
@@ -80,7 +80,7 @@ const Panel = React.createClass({
className={collapseClass}
id={this.props.id}
ref='panel'
- aria-expanded={this.isExpanded() ? 'true' : 'false'}>
+ aria-expanded={this.isExpanded()}>
{this.renderBody()}
- A rendered modal with header, body, and set of actions in the footer. The {''} Component comes with
+ A modal with header, body, and set of actions in the footer. Use {''} in combination with other components to
+ show or hide your Modal. The {''} Component comes with
a few convenient "sub components": {''}, {''}, {''},
and {''}, which you can use to build the Modal content.
+
Additional Import Options
@@ -273,11 +276,6 @@ const ComponentsPage = React.createClass({
import them directly from the /lib directory like: {"require('react-bootstrap/lib/ModalHeader')"}.
-
-
-
Live demo
-
Use {''} in combination with other components to show or hide your Modal.
-
Contained Modal
You will need to add the following css to your project and ensure that your container has the modal-container class.
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index fffdbba1dc..67b1580bd8 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -33,7 +33,6 @@ export default {
PanelGroupUncontrolled: require('fs').readFileSync(__dirname + '/../examples/PanelGroupUncontrolled.js', 'utf8'),
PanelGroupAccordion: require('fs').readFileSync(__dirname + '/../examples/PanelGroupAccordion.js', 'utf8'),
CollapsibleParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsibleParagraph.js', 'utf8'),
- ModalStatic: require('fs').readFileSync(__dirname + '/../examples/ModalStatic.js', 'utf8'),
Modal: require('fs').readFileSync(__dirname + '/../examples/Modal.js', 'utf8'),
ModalContained: require('fs').readFileSync(__dirname + '/../examples/ModalContained.js', 'utf8'),
ModalDefaultSizing: require('fs').readFileSync(__dirname + '/../examples/ModalDefaultSizing.js', 'utf8'),
From cb6fc612217b4fd06e3a8411cd40c5db63a2b61b Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Fri, 17 Jul 2015 14:23:41 -0400
Subject: [PATCH 042/128] Forward animation prop to Overlay from trigger
---
src/OverlayTrigger.js | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/OverlayTrigger.js b/src/OverlayTrigger.js
index b90215f353..783e3b4ee8 100644
--- a/src/OverlayTrigger.js
+++ b/src/OverlayTrigger.js
@@ -21,7 +21,6 @@ function isOneOf(one, of) {
}
const OverlayTrigger = React.createClass({
-
propTypes: {
...Overlay.propTypes,
@@ -142,12 +141,17 @@ const OverlayTrigger = React.createClass({
React.render(this._overlay, this._mountNode);
},
- getOverlay(){
+ getOverlayTarget() {
+ return React.findDOMNode(this);
+ },
+
+ getOverlay() {
let props = {
show: this.state.isOverlayShown,
onHide: this.hide,
rootClose: this.props.rootClose,
- target: ()=> React.findDOMNode(this),
+ animation: this.props.animation,
+ target: this.getOverlayTarget,
placement: this.props.placement,
container: this.props.container,
containerPadding: this.props.containerPadding
From a4385d3fd1f863228458b4b956fdadd469195860 Mon Sep 17 00:00:00 2001
From: jquense
Date: Fri, 17 Jul 2015 14:34:19 -0400
Subject: [PATCH 043/128] [fixed] Portal doesn't mount extra node
fixes #990
---
src/Portal.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Portal.js b/src/Portal.js
index 575f93268b..4cccd243e7 100644
--- a/src/Portal.js
+++ b/src/Portal.js
@@ -24,7 +24,7 @@ let Portal = React.createClass({
componentWillUnmount() {
this._unrenderOverlay();
- this._mountOverlayTarget();
+ this._unmountOverlayTarget();
},
_mountOverlayTarget() {
From fbf9ed64ca2dedd8dde5245e99f9c6b59b8790f4 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Sun, 19 Jul 2015 07:38:24 -0600
Subject: [PATCH 044/128] [changed] Add deprecation warning that factories will
be removed
For details see #825
---
src/templates/factory.index.js.template | 2 ++
src/templates/factory.js.template | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/templates/factory.index.js.template b/src/templates/factory.index.js.template
index de68c1c23e..baaeb1ef02 100644
--- a/src/templates/factory.index.js.template
+++ b/src/templates/factory.index.js.template
@@ -2,6 +2,8 @@
import <%= component %> from './<%= component %>';
<% }); %>
+console.warn('Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
+
export default {
<% _.forEach(components, function (component) { %>
<%= component %>,
diff --git a/src/templates/factory.js.template b/src/templates/factory.js.template
index d89a79c954..051dbd144e 100644
--- a/src/templates/factory.js.template
+++ b/src/templates/factory.js.template
@@ -1,4 +1,6 @@
import React from 'react';
import <%= name %> from '../<%= name %>';
+console.warn('Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
+
export default React.createFactory(<%= name %>);
From c837d8db301ffbe669166c7bd07ec0556ec3dfe5 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Mon, 20 Jul 2015 03:12:37 -0400
Subject: [PATCH 045/128] [fixed] Only calculate overlay position on display
Fixes #1018
---
src/Collapse.js | 72 +++++----
src/Fade.js | 68 ++++----
src/Overlay.js | 96 +++++------
src/Position.js | 93 ++++++-----
src/Transition.js | 350 ++++++++++++++++++++---------------------
test/TransitionSpec.js | 52 ++----
6 files changed, 349 insertions(+), 382 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index 505c116085..299ec8a625 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -115,84 +115,82 @@ class Collapse extends React.Component {
Collapse.propTypes = {
/**
- * Collapse the Component in or out.
+ * Whether the component is entered; triggers the enter or exit animation
*/
- in: React.PropTypes.bool,
+ in: React.PropTypes.bool,
/**
- * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
- * original browser transition end events are canceled.
+ * Whether the component should be unmounted (removed from DOM) when exited
*/
- duration: React.PropTypes.number,
+ unmountOnExit: React.PropTypes.bool,
/**
- * Specifies the dimension used when collapsing.
- *
- * _Note: Bootstrap only partially supports 'width'!
- * You will need to supply your own css animation for the `.width` css class._
+ * Whether transition in should run when the Transition component mounts, if
+ * the component is initially entered
*/
- dimension: React.PropTypes.oneOfType([
- React.PropTypes.oneOf(['height', 'width']),
- React.PropTypes.func
- ]),
+ transitionAppear: React.PropTypes.bool,
/**
- * A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
- * Collapse component should animate in its specified dimension.
- *
- * `getDimensionValue` is called with the current dimension prop value and the DOM node.
+ * Duration of the animation in milliseconds, to ensure that finishing
+ * callbacks are fired even if the original browser transition end events are
+ * canceled
*/
- getDimensionValue: React.PropTypes.func,
+ duration: React.PropTypes.number,
/**
- * A Callback fired before the component starts to expand.
+ * Callback fired before the "entering" classes are applied
*/
onEnter: React.PropTypes.func,
-
/**
- * A Callback fired immediately after the component starts to expand.
+ * Callback fired after the "entering" classes are applied
*/
onEntering: React.PropTypes.func,
-
/**
- * A Callback fired after the component has expanded.
+ * Callback fired after the "enter" classes are applied
*/
onEntered: React.PropTypes.func,
-
/**
- * A Callback fired before the component starts to collapse.
+ * Callback fired before the "exiting" classes are applied
*/
onExit: React.PropTypes.func,
-
/**
- * A Callback fired immediately after the component starts to collapse.
+ * Callback fired after the "exiting" classes are applied
*/
onExiting: React.PropTypes.func,
-
/**
- * A Callback fired after the component has collapsed.
+ * Callback fired after the "exited" classes are applied
*/
onExited: React.PropTypes.func,
/**
- * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ * The dimension used when collapsing
+ *
+ * _Note: Bootstrap only partially supports 'width'!
+ * You will need to supply your own CSS animation for the `.width` CSS class._
*/
- unmountOnExit: React.PropTypes.bool,
+ dimension: React.PropTypes.oneOfType([
+ React.PropTypes.oneOf(['height', 'width']),
+ React.PropTypes.func
+ ]),
/**
- * Specify whether the component should collapse or expand when it mounts.
+ * Function that returns the height or width of the animating DOM node
+ *
+ * Allows for providing some custom logic for how much the Collapse component
+ * should animate in its specified dimension. Called with the current
+ * dimension prop value and the DOM node.
*/
- transitionAppear: React.PropTypes.bool
+ getDimensionValue: React.PropTypes.func
};
Collapse.defaultProps = {
- in: false,
+ in: false,
duration: 300,
- dimension: 'height',
- transitionAppear: false,
unmountOnExit: false,
+ transitionAppear: false,
+
+ dimension: 'height',
getDimensionValue
};
export default Collapse;
-
diff --git a/src/Fade.js b/src/Fade.js
index 523508fff5..528e839d85 100644
--- a/src/Fade.js
+++ b/src/Fade.js
@@ -2,87 +2,77 @@ import React from 'react';
import Transition from './Transition';
class Fade extends React.Component {
-
- constructor(props, context){
- super(props, context);
- }
-
render() {
return (
- { this.props.children }
+ {this.props.children}
);
}
}
+// Explicitly copied from Transition for doc generation.
+
Fade.propTypes = {
/**
- * Fade the Component in or out.
+ * Whether the component is entered; triggers the enter or exit animation
*/
- in: React.PropTypes.bool,
+ in: React.PropTypes.bool,
/**
- * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
- * original browser transition end events are canceled.
+ * Whether the component should be unmounted (removed from DOM) when exited
*/
- duration: React.PropTypes.number,
+ unmountOnExit: React.PropTypes.bool,
/**
- * A Callback fired before the component starts to fade in.
+ * Whether transition in should run when the Transition component mounts, if
+ * the component is initially entered
*/
- onEnter: React.PropTypes.func,
+ transitionAppear: React.PropTypes.bool,
/**
- * A Callback fired immediately after the component has started to faded in.
+ * Duration of the animation in milliseconds, to ensure that finishing
+ * callbacks are fired even if the original browser transition end events are
+ * canceled
*/
- onEntering: React.PropTypes.func,
+ duration: React.PropTypes.number,
/**
- * A Callback fired after the component has faded in.
+ * Callback fired before the "entering" classes are applied
*/
- onEntered: React.PropTypes.func,
-
+ onEnter: React.PropTypes.func,
/**
- * A Callback fired before the component starts to fade out.
+ * Callback fired after the "entering" classes are applied
*/
- onExit: React.PropTypes.func,
-
+ onEntering: React.PropTypes.func,
/**
- * A Callback fired immediately after the component has started to faded out.
+ * Callback fired after the "enter" classes are applied
*/
- onExiting: React.PropTypes.func,
-
+ onEntered: React.PropTypes.func,
/**
- * A Callback fired after the component has faded out.
+ * Callback fired before the "exiting" classes are applied
*/
- onExited: React.PropTypes.func,
-
-
+ onExit: React.PropTypes.func,
/**
- * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ * Callback fired after the "exiting" classes are applied
*/
- unmountOnExit: React.PropTypes.bool,
-
+ onExiting: React.PropTypes.func,
/**
- * Specify whether the component should fade in or out when it mounts.
+ * Callback fired after the "exited" classes are applied
*/
- transitionAppear: React.PropTypes.bool
-
+ onExited: React.PropTypes.func
};
Fade.defaultProps = {
- in: false,
+ in: false,
duration: 300,
- dimension: 'height',
- transitionAppear: false,
- unmountOnExit: false
+ unmountOnExit: false,
+ transitionAppear: false
};
export default Fade;
diff --git a/src/Overlay.js b/src/Overlay.js
index 8e3e996e5b..0a4932903e 100644
--- a/src/Overlay.js
+++ b/src/Overlay.js
@@ -7,31 +7,24 @@ import CustomPropTypes from './utils/CustomPropTypes';
import Fade from './Fade';
import classNames from 'classnames';
-
class Overlay extends React.Component {
-
- constructor(props, context){
+ constructor(props, context) {
super(props, context);
- this.state = { exited: false };
+ this.state = {exited: !props.show};
this.onHiddenListener = this.handleHidden.bind(this);
}
componentWillReceiveProps(nextProps) {
- let state = {};
-
- if ( !nextProps.show && this.props.show ){
- state.exiting = true;
- }
-
if (nextProps.show) {
- state = { exited: false, exiting: false };
+ this.setState({exited: false});
+ } else if (!nextProps.animation) {
+ // Otherwise let handleHidden take care of marking exited.
+ this.setState({exited: true});
}
-
- this.setState(state);
}
- render(){
+ render() {
let {
container
, containerPadding
@@ -42,56 +35,63 @@ class Overlay extends React.Component {
, animation: Transition
, ...props } = this.props;
- let child = null;
-
- if ( Transition === true ){
+ if (Transition === true) {
Transition = Fade;
}
- if (props.show || (Transition && this.state.exiting && !this.state.exited)) {
+ // Don't un-render the overlay while it's transitioning out.
+ const mountOverlay = props.show || (Transition && !this.state.exited);
+
+ if (!mountOverlay) {
+ // Don't bother showing anything if we don't have to.
+ return null;
+ }
- child = children;
+ let child = children;
- // Position the child before the animation to avoid `null` DOM nodes
+ if (Transition) {
+ // This animates the child by injecting props, so it must be inner-most.
child = (
-
- { child }
-
+
+ {child}
+
+ );
+ } else {
+ child = cloneElement(
+ child,
+ {className: classNames('in', child.className)}
);
-
- child = Transition
- ? (
-
- { child }
-
- )
- : cloneElement(child, { className: classNames('in', child.className) });
-
- //Adds a wrapping div so it cannot be before Transition
- if (rootClose) {
- child = (
-
- { child }
-
- );
- }
}
+ // This must wrap the transition to avoid position recalculations.
+ child = (
+
+ {child}
+
+ );
+
+ // This goes after everything else because it adds a wrapping div.
+ if (rootClose) {
+ child = (
+
+ {child}
+
+ );
+ }
return (
- { child }
+ {child}
);
}
- handleHidden(){
- this.setState({ exited: true, exiting: false });
+ handleHidden() {
+ this.setState({exited: true});
}
}
diff --git a/src/Position.js b/src/Position.js
index 6b3b8ff9a0..cb4408e041 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -4,9 +4,9 @@ import { calcOverlayPosition } from './utils/overlayPositionUtils';
import CustomPropTypes from './utils/CustomPropTypes';
class Position extends React.Component {
-
constructor(props, context){
super(props, context);
+
this.state = {
positionLeft: null,
positionTop: null,
@@ -15,33 +15,35 @@ class Position extends React.Component {
};
}
- componentWillMount(){
- this._needsFlush = true;
+ componentDidMount() {
+ this.updatePosition(this.props, this.getTargetSafe(this.props));
}
- componentWillReceiveProps(){
- this._needsFlush = true;
- }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.target !== this.props.target) {
+ const target = this.getTargetSafe(this.props);
+ const nextTarget = this.getTargetSafe(nextProps);
- componentDidMount(){
- this._maybeUpdatePosition();
- }
- componentDidUpdate(){
- this._maybeUpdatePosition();
+ if (nextTarget !== target) {
+ this.updatePosition(nextProps, nextTarget);
+ }
+ }
}
render() {
- let { children, ...props } = this.props;
- let { positionLeft, positionTop, ...arrows } = this.props.target ? this.state : {};
+ const {children, ...props} = this.props;
+ const {positionLeft, positionTop, ...arrowPosition } = this.state;
+ const child = React.Children.only(children);
return cloneElement(
- React.Children.only(children), {
+ child,
+ {
...props,
- ...arrows,
+ ...arrowPosition,
positionTop,
positionLeft,
style: {
- ...children.props.style,
+ ...child.props.style,
left: positionLeft,
top: positionTop
}
@@ -49,55 +51,62 @@ class Position extends React.Component {
);
}
- _maybeUpdatePosition(){
- if ( this._needsFlush ) {
- this._needsFlush = false;
- this._updatePosition();
+ getTargetSafe(props) {
+ if (!props.target) {
+ return null;
}
+
+ return props.target(props);
}
- _updatePosition() {
- if ( this.props.target == null ){
+ updatePosition(props, target) {
+ if (!target) {
+ this.setState({
+ positionLeft: null,
+ positionTop: null,
+ arrowOffsetLeft: null,
+ arrowOffsetTop: null
+ });
+
return;
}
- let overlay = React.findDOMNode(this);
- let target = React.findDOMNode(this.props.target(this.props));
- let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
-
- this.setState(
- calcOverlayPosition(
- this.props.placement
- , overlay
- , target
- , container
- , this.props.containerPadding));
+ const overlay = React.findDOMNode(this);
+ const container =
+ React.findDOMNode(props.container) || domUtils.ownerDocument(this).body;
+
+ this.setState(calcOverlayPosition(
+ props.placement,
+ overlay,
+ target,
+ container,
+ props.containerPadding
+ ));
}
}
Position.propTypes = {
/**
- * The target DOM node the Component is positioned next too.
+ * Function mapping props to DOM node the component is positioned next to
*/
- target: React.PropTypes.func,
+ target: React.PropTypes.func,
/**
- * The "offsetParent" of the Component
+ * "offsetParent" of the component
*/
- container: CustomPropTypes.mountable,
+ container: CustomPropTypes.mountable,
/**
- * Distance in pixels the Component should be positioned to the edge of the Container.
+ * Minimum spacing in pixels between container border and component border
*/
containerPadding: React.PropTypes.number,
/**
- * The location that the overlay should be positioned to its target.
+ * How to position the component relative to the target
*/
- placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left'])
+ placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left'])
};
Position.defaultProps = {
containerPadding: 0,
- placement: 'right'
+ placement: 'right'
};
-
export default Position;
diff --git a/src/Transition.js b/src/Transition.js
index a49124aac7..85eb05c5f6 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -2,284 +2,274 @@ import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
import classnames from 'classnames';
-function omit(obj, keys) {
- let included = Object.keys(obj).filter( k => keys.indexOf(k) === -1);
- let newObj = {};
-
- included.forEach( key => newObj[key] = obj[key] );
- return newObj;
-}
-
-function ensureTransitionEnd(node, handler, duration){
- let fired = false;
- let done = e => {
- if (!fired) {
- fired = true;
- handler(e);
- }
- };
-
- if ( node ) {
- TransitionEvents.addEndEventListener(node, done);
- setTimeout(done, duration);
- } else {
- setTimeout(done, 0);
- }
-}
-
-// reading a dimension prop will cause the browser to recalculate,
-// which will let our animations work
-let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+const UNMOUNTED = 0;
+export const EXITED = 1;
+export const ENTERING = 2;
+export const ENTERED = 3;
+export const EXITING = 4;
class Transition extends React.Component {
-
- constructor(props, context){
+ constructor(props, context) {
super(props, context);
- this.state = {
- in: !props.in,
- transitioning: false
- };
+ let initialStatus;
+ if (props.in) {
+ // Perform enter in performNextTransition from componentDidMount.
+ initialStatus = props.transitionAppear ? EXITED : ENTERED;
+ } else {
+ initialStatus = props.unmountOnExit ? UNMOUNTED : EXITED;
+ }
+ this.state = {status: initialStatus};
- this.needsTransition = true;
+ this.nextCallback = null;
}
- componentWillReceiveProps(nextProps) {
- if (nextProps.in !== this.props.in) {
- this.needsTransition = true;
- }
+ componentDidMount() {
+ this.performNextTransition();
}
- componentDidUpdate() {
- this.processChild();
- }
+ componentWillReceiveProps(nextProps) {
+ const status = this.state.status;
+ if (nextProps.in) {
+ if (status === EXITING) {
+ this.performEnter(nextProps);
+ } else if (this.props.unmountOnExit) {
+ // Perform enter in performNextTransition.
+ if (status === UNMOUNTED) {
+ this.setState({status: EXITED});
+ }
+ } else if (status === EXITED) {
+ this.performEnter(nextProps);
+ }
- componentWillMount() {
- this._mounted = true;
+ // Otherwise we're already entering or entered.
+ } else {
+ if (status === ENTERING || status === ENTERED) {
+ this.performExit(nextProps);
+ }
- if (!this.props.transitionAppear) {
- this.needsTransition = false;
- this.setState({ in: this.props.in });
+ // Otherwise we're already exited or exiting.
}
}
- componentWillUnmount(){
- this._mounted = false;
+ componentDidUpdate() {
+ this.performNextTransition();
}
- componentDidMount() {
- if (this.props.transitionAppear) {
- this.processChild();
- }
+ componentWillUnmount() {
+ this.cancelNextCallback();
}
- processChild(){
- let needsTransition = this.needsTransition;
- let enter = this.props.in;
-
- if (needsTransition) {
- this.needsTransition = false;
- this[enter ? 'performEnter' : 'performLeave']();
+ performNextTransition() {
+ if (this.state.status === EXITED) {
+ if (this.props.in) {
+ // Either because of transitionAppear or unmountOnExit.
+ this.performEnter(this.props);
+ } else if (this.props.unmountOnExit) {
+ this.setState({status: UNMOUNTED});
+ }
}
}
- performEnter() {
- let maybeNode = React.findDOMNode(this);
-
- let enter = node => {
- node = this.props.transitioningNode(node) || node;
-
- this.props.onEnter(node);
+ performEnter(props) {
+ this.cancelNextCallback();
+ const node = React.findDOMNode(this);
- this.safeSetState({ in: true, transitioning: true, needInitialRender: false }, ()=> {
+ // Not this.props, because we might be about to receive new props.
+ props.onEnter(node);
- this.props.onEntering(node);
+ this.safeSetState({status: ENTERING}, () => {
+ this.props.onEntering(node);
- ensureTransitionEnd(node, () => {
- if ( this.state.in ){
- this.safeSetState({
- transitioning: false
- }, () => this.props.onEntered(node));
- }
-
- }, this.props.duration);
+ this.onTransitionEnd(node, () => {
+ this.safeSetState({status: ENTERED}, () => {
+ this.props.onEntered(node);
+ });
});
- };
-
- if (maybeNode) {
- enter(maybeNode);
- }
- else if (this.props.unmountOnExit) {
- this._ensureNode(enter);
- }
+ });
}
- performLeave() {
- let node = React.findDOMNode(this);
-
- node = this.props.transitioningNode(node) || node;
+ performExit(props) {
+ this.cancelNextCallback();
+ const node = React.findDOMNode(this);
- this.props.onExit(node);
+ // Not this.props, because we might be about to receive new props.
+ props.onExit(node);
- this.setState({ in: false, transitioning: true }, () => {
+ this.safeSetState({status: EXITING}, () => {
this.props.onExiting(node);
- ensureTransitionEnd(node, () => {
- if ( !this.state.in ){
- this.safeSetState({ transitioning: false }, ()=> this.props.onExited(node));
- }
- }, this.props.duration);
+ this.onTransitionEnd(node, () => {
+ this.safeSetState({status: EXITED}, () => {
+ this.props.onExited(node);
+ });
+ });
});
}
- _ensureNode(callback) {
-
- this.setState({ needInitialRender: true }, ()=> {
- let node = React.findDOMNode(this);
-
- triggerBrowserReflow(node);
-
- callback(node);
- });
+ cancelNextCallback() {
+ if (this.nextCallback !== null) {
+ this.nextCallback.cancel();
+ this.nextCallback = null;
+ }
}
- safeSetState(newState, cb){
- if (this._mounted) {
- this.setState(newState, cb);
- }
+ safeSetState(nextState, callback) {
+ // This shouldn't be necessary, but there are weird race conditions with
+ // setState callbacks and unmounting in testing, so always make sure that
+ // we can cancel any pending setState callbacks after we unmount.
+ this.setState(nextState, this.setNextCallback(callback));
}
- render() {
- let childProps = omit(this.props, Object.keys(Transition.propTypes).concat('children'));
+ setNextCallback(callback) {
+ let active = true;
- let child = this.props.children;
- let starting = this.state.needInitialRender;
- let out = !this.state.in && !this.state.transitioning;
+ this.nextCallback = (event) => {
+ if (active) {
+ active = false;
+ this.nextCallback = null;
- if ( !child || (this.props.unmountOnExit && out && !starting) ){
- return null;
- }
+ callback(event);
+ }
+ };
- let classes = '';
+ this.nextCallback.cancel = () => {
+ active = false;
+ };
- // using `classnames()` here causes a subtle bug,
- // hence the verbose if/else if sequence.
- if (this.state.in && !this.state.transitioning) {
- classes = this.props.enteredClassName;
- }
+ return this.nextCallback;
+ }
+
+ onTransitionEnd(node, handler) {
+ this.setNextCallback(handler);
- else if (this.state.in && this.state.transitioning) {
- classes = this.props.enteringClassName;
+ if (node) {
+ TransitionEvents.addEndEventListener(node, this.nextCallback);
+ setTimeout(this.nextCallback, this.props.duration);
+ } else {
+ setTimeout(this.nextCallback, 0);
}
+ }
- else if (!this.state.in && !this.state.transitioning) {
- classes = this.props.exitedClassName;
+ render() {
+ const status = this.state.status;
+ if (status === UNMOUNTED) {
+ return null;
}
- else if (!this.state.in && this.state.transitioning) {
- classes = this.props.exitingClassName;
+ const {children, className, style, ...childProps} = this.props;
+ Object.keys(Transition.propTypes).forEach(key => delete childProps[key]);
+
+ let transitionClassName;
+ if (status === EXITED) {
+ transitionClassName = this.props.exitedClassName;
+ } else if (status === ENTERING) {
+ transitionClassName = this.props.enteringClassName;
+ } else if (status === ENTERED) {
+ transitionClassName = this.props.enteredClassName;
+ } else if (status === EXITING) {
+ transitionClassName = this.props.exitingClassName;
}
- return React.cloneElement(child, {
- ...childProps,
- className: classnames(
- child.props.className,
- this.props.className,
- classes)
- });
+ const child = React.Children.only(children);
+ return React.cloneElement(
+ child,
+ {
+ ...childProps,
+ className: classnames(
+ child.props.className,
+ className,
+ transitionClassName
+ ),
+ style: {...child.props.style, ...style}
+ }
+ );
}
}
Transition.propTypes = {
/**
- * Triggers the Enter or Exit animation
+ * Whether the component is entered; triggers the enter or exit animation
*/
- in: React.PropTypes.bool,
+ in: React.PropTypes.bool,
/**
- * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ * Whether the component should be unmounted (removed from DOM) when exited
*/
- unmountOnExit: React.PropTypes.bool,
+ unmountOnExit: React.PropTypes.bool,
/**
- * Specify whether transitions should run when the Transition component mounts.
+ * Whether transition in should run when the Transition component mounts, if
+ * the component is initially entered
*/
transitionAppear: React.PropTypes.bool,
/**
- * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
- * original browser transition end events are canceled.
+ * Duration of the animation in milliseconds, to ensure that finishing
+ * callbacks are fired even if the original browser transition end events are
+ * canceled
*/
- duration: React.PropTypes.number,
+ duration: React.PropTypes.number,
/**
- * A css class or classes applied once the Component has exited.
+ * CSS class or classes applied when the component is exited
*/
- exitedClassName: React.PropTypes.string,
+ exitedClassName: React.PropTypes.string,
/**
- * A css class or classes applied while the Component is exiting.
+ * CSS class or classes applied while the component is exiting
*/
- exitingClassName: React.PropTypes.string,
+ exitingClassName: React.PropTypes.string,
/**
- * A css class or classes applied once the Component has entered.
+ * CSS class or classes applied when the component is entered
*/
- enteredClassName: React.PropTypes.string,
+ enteredClassName: React.PropTypes.string,
/**
- * A css class or classes applied while the Component is entering.
+ * CSS class or classes applied while the component is entering
*/
enteringClassName: React.PropTypes.string,
/**
- * A function that returns the DOM node to animate. This Node will have the transition classes applied to it.
- * When left out, the Component will use its immediate child.
- *
- * @private
- */
- transitioningNode: React.PropTypes.func,
-
- /**
- * A callback fired just before the "entering" classes are applied
+ * Callback fired before the "entering" classes are applied
*/
- onEnter: React.PropTypes.func,
+ onEnter: React.PropTypes.func,
/**
- * A callback fired just after the "entering" classes are applied
+ * Callback fired after the "entering" classes are applied
*/
- onEntering: React.PropTypes.func,
+ onEntering: React.PropTypes.func,
/**
- * A callback fired after "enter" classes are applied
+ * Callback fired after the "enter" classes are applied
*/
- onEntered: React.PropTypes.func,
+ onEntered: React.PropTypes.func,
/**
- * A callback fired after "exiting" classes are applied
+ * Callback fired before the "exiting" classes are applied
*/
- onExit: React.PropTypes.func,
+ onExit: React.PropTypes.func,
/**
- * A callback fired after "exiting" classes are applied
+ * Callback fired after the "exiting" classes are applied
*/
- onExiting: React.PropTypes.func,
+ onExiting: React.PropTypes.func,
/**
- * A callback fired after "exit" classes are applied
+ * Callback fired after the "exited" classes are applied
*/
- onExited: React.PropTypes.func
+ onExited: React.PropTypes.func
};
-// name the function so it is clearer in the documentation
-const noop = ()=>{};
+// Name the function so it is clearer in the documentation
+function noop() {}
Transition.defaultProps = {
- in: false,
+ in: false,
duration: 300,
unmountOnExit: false,
transitionAppear: false,
- transitioningNode: noop,
- onEnter: noop,
+ onEnter: noop,
onEntering: noop,
- onEntered: noop,
+ onEntered: noop,
- onExit: noop,
- onExiting: noop,
- onExited: noop
+ onExit: noop,
+ onExiting: noop,
+ onExited: noop
};
export default Transition;
diff --git a/test/TransitionSpec.js b/test/TransitionSpec.js
index 7945ba8ceb..ced505b537 100644
--- a/test/TransitionSpec.js
+++ b/test/TransitionSpec.js
@@ -1,12 +1,10 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import { render } from './helpers';
-import Transition from '../src/Transition';
-//import classNames from 'classnames';
+import Transition, {EXITED, ENTERING, ENTERED, EXITING} from
+ '../src/Transition';
describe('Transition', function () {
-
-
it('should not transition on mount', function(){
let instance = render(
{ throw new Error('should not Enter'); }}>
@@ -14,8 +12,7 @@ describe('Transition', function () {
);
- instance.state.in.should.equal(true);
- assert.ok(!instance.state.transitioning);
+ instance.state.status.should.equal(ENTERED);
});
it('should transition on mount with transitionAppear', done =>{
@@ -28,8 +25,7 @@ describe('Transition', function () {
);
- instance.state.in.should.equal(true);
- instance.state.transitioning.should.equal(true);
+ instance.state.status.should.equal(EXITED);
});
describe('entering', ()=> {
@@ -51,10 +47,9 @@ describe('Transition', function () {
let onEnter = sinon.spy();
let onEntering = sinon.spy();
- instance.state.in.should.equal(false);
+ instance.state.status.should.equal(EXITED);
instance = instance.renderWithProps({
-
in: true,
onEnter,
@@ -73,27 +68,23 @@ describe('Transition', function () {
it('should move to each transition state', done => {
let count = 0;
- instance.state.in.should.equal(false);
+ instance.state.status.should.equal(EXITED);
instance = instance.renderWithProps({
-
in: true,
onEnter(){
count++;
- instance.state.in.should.equal(false);
- instance.state.transitioning.should.equal(false);
+ instance.state.status.should.equal(EXITED);
},
onEntering(){
count++;
- instance.state.in.should.equal(true);
- instance.state.transitioning.should.equal(true);
+ instance.state.status.should.equal(ENTERING);
},
onEntered(){
- instance.state.in.should.equal(true);
- instance.state.transitioning.should.equal(false);
+ instance.state.status.should.equal(ENTERED);
assert.ok(count === 2);
done();
}
@@ -103,10 +94,9 @@ describe('Transition', function () {
it('should apply classes at each transition state', done => {
let count = 0;
- instance.state.in.should.equal(false);
+ instance.state.status.should.equal(EXITED);
instance = instance.renderWithProps({
-
in: true,
onEnter(node){
@@ -126,10 +116,8 @@ describe('Transition', function () {
}
});
});
-
});
-
describe('exiting', ()=> {
let instance;
@@ -150,10 +138,9 @@ describe('Transition', function () {
let onExit = sinon.spy();
let onExiting = sinon.spy();
- instance.state.in.should.equal(true);
+ instance.state.status.should.equal(ENTERED);
instance = instance.renderWithProps({
-
in: false,
onExit,
@@ -172,27 +159,23 @@ describe('Transition', function () {
it('should move to each transition state', done => {
let count = 0;
- instance.state.in.should.equal(true);
+ instance.state.status.should.equal(ENTERED);
instance = instance.renderWithProps({
-
in: false,
onExit(){
count++;
- instance.state.in.should.equal(true);
- instance.state.transitioning.should.equal(false);
+ instance.state.status.should.equal(ENTERED);
},
onExiting(){
count++;
- instance.state.in.should.equal(false);
- instance.state.transitioning.should.equal(true);
+ instance.state.status.should.equal(EXITING);
},
onExited(){
- instance.state.in.should.equal(false);
- instance.state.transitioning.should.equal(false);
+ instance.state.status.should.equal(EXITED);
//assert.ok(count === 2);
done();
}
@@ -202,10 +185,9 @@ describe('Transition', function () {
it('should apply classes at each transition state', done => {
let count = 0;
- instance.state.in.should.equal(true);
+ instance.state.status.should.equal(ENTERED);
instance = instance.renderWithProps({
-
in: false,
onExit(node){
@@ -225,7 +207,5 @@ describe('Transition', function () {
}
});
});
-
});
-
});
From dd064ad4f2833d29d07189c0677e0da0b1b4b503 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Mon, 20 Jul 2015 19:10:22 +0300
Subject: [PATCH 046/128] [fixed] remove extraneous styling
---
src/ModalHeader.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/ModalHeader.js b/src/ModalHeader.js
index 706546c64f..0ab2dda5cf 100644
--- a/src/ModalHeader.js
+++ b/src/ModalHeader.js
@@ -15,7 +15,6 @@ class ModalHeader extends React.Component {
className='close'
aria-label={this.props['aria-label'] || 'Close'} //eslint-disable-line react/prop-types
onClick={this.props.onHide}
- style={{ marginTop: -2 }}
>
×
From dbacfd9d95f54647e8f4abc4172f6141c83d98e1 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Mon, 20 Jul 2015 12:54:40 -0400
Subject: [PATCH 047/128] !fixup fixes to previous commit
---
src/Collapse.js | 32 ++++++++++++++++++--------------
src/Fade.js | 23 ++++++++++++-----------
src/Overlay.js | 19 ++++++++++---------
src/Transition.js | 36 +++++++++++++++++-------------------
4 files changed, 57 insertions(+), 53 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index 299ec8a625..30f0cbaa32 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -113,57 +113,61 @@ class Collapse extends React.Component {
}
}
+// Explicitly copied from Transition for doc generation.
+// TODO: Remove duplication once #977 is resolved.
+
Collapse.propTypes = {
/**
- * Whether the component is entered; triggers the enter or exit animation
+ * Whether the component is expanded
*/
in: React.PropTypes.bool,
/**
- * Whether the component should be unmounted (removed from DOM) when exited
+ * Whether the component should be unmounted (removed from DOM) when
+ * collapsed
*/
unmountOnExit: React.PropTypes.bool,
/**
- * Whether transition in should run when the Transition component mounts, if
- * the component is initially entered
+ * Whether the component should expand after mounting
*/
transitionAppear: React.PropTypes.bool,
/**
- * Duration of the animation in milliseconds, to ensure that finishing
- * callbacks are fired even if the original browser transition end events are
- * canceled
+ * Duration of the collapse animation in milliseconds, to ensure that
+ * finishing callbacks are fired even if the original browser transition end
+ * events are canceled
*/
duration: React.PropTypes.number,
/**
- * Callback fired before the "entering" classes are applied
+ * Callback fired before the component expands
*/
onEnter: React.PropTypes.func,
/**
- * Callback fired after the "entering" classes are applied
+ * Callback fired after the component starts to expand
*/
onEntering: React.PropTypes.func,
/**
- * Callback fired after the "enter" classes are applied
+ * Callback fired after the component has expanded
*/
onEntered: React.PropTypes.func,
/**
- * Callback fired before the "exiting" classes are applied
+ * Callback fired before the component collapses
*/
onExit: React.PropTypes.func,
/**
- * Callback fired after the "exiting" classes are applied
+ * Callback fired after the component starts to collapse
*/
onExiting: React.PropTypes.func,
/**
- * Callback fired after the "exited" classes are applied
+ * Callback fired after the component has collapsed
*/
onExited: React.PropTypes.func,
/**
- * The dimension used when collapsing
+ * The dimension used when collapsing, or a function that returns the
+ * dimension
*
* _Note: Bootstrap only partially supports 'width'!
* You will need to supply your own CSS animation for the `.width` CSS class._
diff --git a/src/Fade.js b/src/Fade.js
index 528e839d85..9e8df40b19 100644
--- a/src/Fade.js
+++ b/src/Fade.js
@@ -17,53 +17,54 @@ class Fade extends React.Component {
}
// Explicitly copied from Transition for doc generation.
+// TODO: Remove duplication once #977 is resolved.
Fade.propTypes = {
/**
- * Whether the component is entered; triggers the enter or exit animation
+ * Whether the component is faded in
*/
in: React.PropTypes.bool,
/**
- * Whether the component should be unmounted (removed from DOM) when exited
+ * Whether the component should be unmounted (removed from DOM) when faded
+ * out
*/
unmountOnExit: React.PropTypes.bool,
/**
- * Whether transition in should run when the Transition component mounts, if
- * the component is initially entered
+ * Whether the component should fade in after mounting
*/
transitionAppear: React.PropTypes.bool,
/**
- * Duration of the animation in milliseconds, to ensure that finishing
+ * Duration of the fade animation in milliseconds, to ensure that finishing
* callbacks are fired even if the original browser transition end events are
* canceled
*/
duration: React.PropTypes.number,
/**
- * Callback fired before the "entering" classes are applied
+ * Callback fired before the component fades in
*/
onEnter: React.PropTypes.func,
/**
- * Callback fired after the "entering" classes are applied
+ * Callback fired after the component starts to fade in
*/
onEntering: React.PropTypes.func,
/**
- * Callback fired after the "enter" classes are applied
+ * Callback fired after the has component faded in
*/
onEntered: React.PropTypes.func,
/**
- * Callback fired before the "exiting" classes are applied
+ * Callback fired before the component fades out
*/
onExit: React.PropTypes.func,
/**
- * Callback fired after the "exiting" classes are applied
+ * Callback fired after the component starts to fade out
*/
onExiting: React.PropTypes.func,
/**
- * Callback fired after the "exited" classes are applied
+ * Callback fired after the component has faded out
*/
onExited: React.PropTypes.func
};
diff --git a/src/Overlay.js b/src/Overlay.js
index 0a4932903e..6d82a7f401 100644
--- a/src/Overlay.js
+++ b/src/Overlay.js
@@ -41,7 +41,6 @@ class Overlay extends React.Component {
// Don't un-render the overlay while it's transitioning out.
const mountOverlay = props.show || (Transition && !this.state.exited);
-
if (!mountOverlay) {
// Don't bother showing anything if we don't have to.
return null;
@@ -49,8 +48,17 @@ class Overlay extends React.Component {
let child = children;
+ // Position is be inner-most because it adds inline styles into the child,
+ // which the other wrappers don't forward correctly.
+ child = (
+
+ {child}
+
+ );
+
if (Transition) {
- // This animates the child by injecting props, so it must be inner-most.
+ // This animates the child node by injecting props, so it must precede
+ // anything that adds a wrapping div.
child = (
- {child}
-
- );
-
// This goes after everything else because it adds a wrapping div.
if (rootClose) {
child = (
diff --git a/src/Transition.js b/src/Transition.js
index 85eb05c5f6..260628997a 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -14,7 +14,7 @@ class Transition extends React.Component {
let initialStatus;
if (props.in) {
- // Perform enter in performNextTransition from componentDidMount.
+ // Start enter transition in componentDidMount.
initialStatus = props.transitionAppear ? EXITED : ENTERED;
} else {
initialStatus = props.unmountOnExit ? UNMOUNTED : EXITED;
@@ -25,7 +25,9 @@ class Transition extends React.Component {
}
componentDidMount() {
- this.performNextTransition();
+ if (this.props.transitionAppear && this.props.in) {
+ this.performEnter(this.props);
+ }
}
componentWillReceiveProps(nextProps) {
@@ -34,8 +36,8 @@ class Transition extends React.Component {
if (status === EXITING) {
this.performEnter(nextProps);
} else if (this.props.unmountOnExit) {
- // Perform enter in performNextTransition.
if (status === UNMOUNTED) {
+ // Start enter transition in componentDidUpdate.
this.setState({status: EXITED});
}
} else if (status === EXITED) {
@@ -53,24 +55,21 @@ class Transition extends React.Component {
}
componentDidUpdate() {
- this.performNextTransition();
- }
-
- componentWillUnmount() {
- this.cancelNextCallback();
- }
-
- performNextTransition() {
- if (this.state.status === EXITED) {
+ if (this.props.unmountOnExit && this.state.status === EXITED) {
+ // EXITED is always a transitional state to either ENTERING or UNMOUNTED
+ // when using unmountOnExit.
if (this.props.in) {
- // Either because of transitionAppear or unmountOnExit.
this.performEnter(this.props);
- } else if (this.props.unmountOnExit) {
+ } else {
this.setState({status: UNMOUNTED});
}
}
}
+ componentWillUnmount() {
+ this.cancelNextCallback();
+ }
+
performEnter(props) {
this.cancelNextCallback();
const node = React.findDOMNode(this);
@@ -157,7 +156,7 @@ class Transition extends React.Component {
return null;
}
- const {children, className, style, ...childProps} = this.props;
+ const {children, className, ...childProps} = this.props;
Object.keys(Transition.propTypes).forEach(key => delete childProps[key]);
let transitionClassName;
@@ -180,8 +179,7 @@ class Transition extends React.Component {
child.props.className,
className,
transitionClassName
- ),
- style: {...child.props.style, ...style}
+ )
}
);
}
@@ -189,7 +187,7 @@ class Transition extends React.Component {
Transition.propTypes = {
/**
- * Whether the component is entered; triggers the enter or exit animation
+ * Whether the component is shown; triggers the enter or exit animation
*/
in: React.PropTypes.bool,
@@ -200,7 +198,7 @@ Transition.propTypes = {
/**
* Whether transition in should run when the Transition component mounts, if
- * the component is initially entered
+ * the component is initially shown
*/
transitionAppear: React.PropTypes.bool,
From 25c8efab9c2046e98fe36a5c95fcbf5bb7fe9587 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Mon, 20 Jul 2015 20:05:55 +0300
Subject: [PATCH 048/128] Fix test case
---
test/ModalSpec.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 5a9b78f520..66bb5be9e2 100644
--- a/test/ModalSpec.js
+++ b/test/ModalSpec.js
@@ -58,7 +58,7 @@ describe('Modal', function () {
let modal = ReactTestUtils.findRenderedComponentWithType(instance, Modal);
- assert.ok(React.findDOMNode(instance).className.match(/\modal-open\b/));
+ assert.ok(React.findDOMNode(instance).className.match(/\bmodal-open\b/));
let backdrop = React.findDOMNode(modal.refs.modal).getElementsByClassName('modal-backdrop')[0];
From 27680616630c1e2ef1c533018d72773e8689c8f0 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Sun, 19 Jul 2015 21:55:54 +0300
Subject: [PATCH 049/128] Make deprecation warnings safe
---
src/templates/factory.index.js.template | 4 +++-
src/templates/factory.js.template | 3 ++-
src/utils/deprecationWarning.js | 17 +++++++----------
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/templates/factory.index.js.template b/src/templates/factory.index.js.template
index baaeb1ef02..ba70624988 100644
--- a/src/templates/factory.index.js.template
+++ b/src/templates/factory.index.js.template
@@ -1,8 +1,10 @@
+import warning from 'react/lib/warning';
+
<% _.forEach(components, function (component) { %>
import <%= component %> from './<%= component %>';
<% }); %>
-console.warn('Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
+warning(false, 'Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
export default {
<% _.forEach(components, function (component) { %>
diff --git a/src/templates/factory.js.template b/src/templates/factory.js.template
index 051dbd144e..d9a3042ba8 100644
--- a/src/templates/factory.js.template
+++ b/src/templates/factory.js.template
@@ -1,6 +1,7 @@
import React from 'react';
+import warning from 'react/lib/warning';
import <%= name %> from '../<%= name %>';
-console.warn('Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
+warning(false, 'Support for factories will be removed in v0.25, for details see https://github.com/react-bootstrap/react-bootstrap/issues/825');
export default React.createFactory(<%= name %>);
diff --git a/src/utils/deprecationWarning.js b/src/utils/deprecationWarning.js
index 802158410f..e92b75837d 100644
--- a/src/utils/deprecationWarning.js
+++ b/src/utils/deprecationWarning.js
@@ -1,14 +1,11 @@
-export default function deprecationWarning(oldname, newname, link) {
- if (process.env.NODE_ENV !== 'production') {
- if ((typeof console === 'undefined') || (typeof console.warn !== 'function')) {
- return;
- }
+import warning from 'react/lib/warning';
- let message = `${oldname} is deprecated. Use ${newname} instead.`;
- console.warn(message);
+export default function deprecationWarning(oldname, newname, link) {
+ let message = `${oldname} is deprecated. Use ${newname} instead.`;
- if (link) {
- console.warn(`You can read more about it at ${link}`);
- }
+ if (link) {
+ message += `\nYou can read more about it at ${link}`;
}
+
+ warning(false, message);
}
From afe9d9c255840c2f87034e7cb18656420074bf73 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Mon, 20 Jul 2015 13:37:32 -0400
Subject: [PATCH 050/128] !fixup add unmountOnExit test cases
---
src/Transition.js | 2 +-
test/TransitionSpec.js | 130 +++++++++++++++++++++++++++++++----------
2 files changed, 100 insertions(+), 32 deletions(-)
diff --git a/src/Transition.js b/src/Transition.js
index 260628997a..f1c557f4ed 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -2,7 +2,7 @@ import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
import classnames from 'classnames';
-const UNMOUNTED = 0;
+export const UNMOUNTED = 0;
export const EXITED = 1;
export const ENTERING = 2;
export const ENTERED = 3;
diff --git a/test/TransitionSpec.js b/test/TransitionSpec.js
index ced505b537..1592e646f7 100644
--- a/test/TransitionSpec.js
+++ b/test/TransitionSpec.js
@@ -1,7 +1,7 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import { render } from './helpers';
-import Transition, {EXITED, ENTERING, ENTERED, EXITING} from
+import Transition, {UNMOUNTED, EXITED, ENTERING, ENTERED, EXITING} from
'../src/Transition';
describe('Transition', function () {
@@ -12,7 +12,7 @@ describe('Transition', function () {
);
- instance.state.status.should.equal(ENTERED);
+ expect(instance.state.status).to.equal(ENTERED);
});
it('should transition on mount with transitionAppear', done =>{
@@ -25,7 +25,7 @@ describe('Transition', function () {
);
- instance.state.status.should.equal(EXITED);
+ expect(instance.state.status).to.equal(EXITED);
});
describe('entering', ()=> {
@@ -47,7 +47,7 @@ describe('Transition', function () {
let onEnter = sinon.spy();
let onEntering = sinon.spy();
- instance.state.status.should.equal(EXITED);
+ expect(instance.state.status).to.equal(EXITED);
instance = instance.renderWithProps({
in: true,
@@ -57,9 +57,9 @@ describe('Transition', function () {
onEntering,
onEntered(){
- assert.ok(onEnter.calledOnce);
- assert.ok(onEntering.calledOnce);
- assert.ok(onEnter.calledBefore(onEntering));
+ expect(onEnter.calledOnce).to.be.ok;
+ expect(onEntering.calledOnce).to.be.ok;
+ expect(onEnter.calledBefore(onEntering)).to.be.ok;
done();
}
});
@@ -68,24 +68,24 @@ describe('Transition', function () {
it('should move to each transition state', done => {
let count = 0;
- instance.state.status.should.equal(EXITED);
+ expect(instance.state.status).to.equal(EXITED);
instance = instance.renderWithProps({
in: true,
onEnter(){
count++;
- instance.state.status.should.equal(EXITED);
+ expect(instance.state.status).to.equal(EXITED);
},
onEntering(){
count++;
- instance.state.status.should.equal(ENTERING);
+ expect(instance.state.status).to.equal(ENTERING);
},
onEntered(){
- instance.state.status.should.equal(ENTERED);
- assert.ok(count === 2);
+ expect(instance.state.status).to.equal(ENTERED);
+ expect(count).to.equal(2);
done();
}
});
@@ -94,24 +94,24 @@ describe('Transition', function () {
it('should apply classes at each transition state', done => {
let count = 0;
- instance.state.status.should.equal(EXITED);
+ expect(instance.state.status).to.equal(EXITED);
instance = instance.renderWithProps({
in: true,
onEnter(node){
count++;
- assert.equal(node.className, '');
+ expect(node.className).to.equal('');
},
onEntering(node){
count++;
- assert.equal(node.className, 'test-entering');
+ expect(node.className).to.equal('test-entering');
},
onEntered(node){
- assert.equal(node.className, 'test-enter');
- assert.ok(count === 2);
+ expect(node.className).to.equal('test-enter');
+ expect(count).to.equal(2);
done();
}
});
@@ -138,7 +138,7 @@ describe('Transition', function () {
let onExit = sinon.spy();
let onExiting = sinon.spy();
- instance.state.status.should.equal(ENTERED);
+ expect(instance.state.status).to.equal(ENTERED);
instance = instance.renderWithProps({
in: false,
@@ -148,9 +148,9 @@ describe('Transition', function () {
onExiting,
onExited(){
- assert.ok(onExit.calledOnce);
- assert.ok(onExiting.calledOnce);
- assert.ok(onExit.calledBefore(onExiting));
+ expect(onExit.calledOnce).to.be.ok;
+ expect(onExiting.calledOnce).to.be.ok;
+ expect(onExit.calledBefore(onExiting)).to.be.ok;
done();
}
});
@@ -159,24 +159,24 @@ describe('Transition', function () {
it('should move to each transition state', done => {
let count = 0;
- instance.state.status.should.equal(ENTERED);
+ expect(instance.state.status).to.equal(ENTERED);
instance = instance.renderWithProps({
in: false,
onExit(){
count++;
- instance.state.status.should.equal(ENTERED);
+ expect(instance.state.status).to.equal(ENTERED);
},
onExiting(){
count++;
- instance.state.status.should.equal(EXITING);
+ expect(instance.state.status).to.equal(EXITING);
},
onExited(){
- instance.state.status.should.equal(EXITED);
- //assert.ok(count === 2);
+ expect(instance.state.status).to.equal(EXITED);
+ expect(count).to.equal(2);
done();
}
});
@@ -185,27 +185,95 @@ describe('Transition', function () {
it('should apply classes at each transition state', done => {
let count = 0;
- instance.state.status.should.equal(ENTERED);
+ expect(instance.state.status).to.equal(ENTERED);
instance = instance.renderWithProps({
in: false,
onExit(node){
count++;
- assert.equal(node.className, '');
+ expect(node.className).to.equal('');
},
onExiting(node){
count++;
- assert.equal(node.className, 'test-exiting');
+ expect(node.className).to.equal('test-exiting');
},
onExited(node){
- assert.equal(node.className, 'test-exit');
- assert.ok(count === 2);
+ expect(node.className).to.equal('test-exit');
+ expect(count).to.equal(2);
done();
}
});
});
});
+
+ describe('unmountOnExit', () => {
+ class UnmountTransition extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {in: props.initialIn};
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+
+ getStatus() {
+ return this.refs.transition.state.status;
+ }
+ }
+
+ it('should mount when entering', done => {
+ const instance = render(
+ {
+ expect(instance.getStatus()).to.equal(EXITED);
+ expect(React.findDOMNode(instance)).to.exist;
+
+ done();
+ }}
+ />
+ );
+
+ expect(instance.getStatus()).to.equal(UNMOUNTED);
+ expect(React.findDOMNode(instance)).to.not.exist;
+
+ instance.setState({in: true});
+ });
+
+ it('should unmount after exiting', done => {
+ const instance = render(
+ {
+ expect(instance.getStatus()).to.equal(UNMOUNTED);
+ expect(React.findDOMNode(instance)).to.not.exist;
+
+ done();
+ }}
+ />
+ );
+
+ expect(instance.getStatus()).to.equal(ENTERED);
+ expect(React.findDOMNode(instance)).to.exist;
+
+ instance.setState({in: false});
+ });
+ });
});
From 924f8fb619dde3beb6f385769ea7633cf606daf8 Mon Sep 17 00:00:00 2001
From: Geoff Pleiss and Kenny Wang
Date: Mon, 20 Jul 2015 15:33:08 -0700
Subject: [PATCH 051/128] [fixed] Tooltip accepts a style prop
fixes #1027
---
src/Tooltip.js | 9 +++++++--
test/TooltipSpec.js | 15 ++++++++++++++-
2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/src/Tooltip.js b/src/Tooltip.js
index 0cecc0dc9b..9d78e6f1ec 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -43,7 +43,11 @@ const Tooltip = React.createClass({
/**
* Title text
*/
- title: React.PropTypes.node
+ title: React.PropTypes.node,
+ /**
+ * Style hash
+ */
+ style: React.PropTypes.object
},
getDefaultProps() {
@@ -60,7 +64,8 @@ const Tooltip = React.createClass({
const style = {
'left': this.props.positionLeft,
- 'top': this.props.positionTop
+ 'top': this.props.positionTop,
+ ...this.props.style
};
const arrowStyle = {
diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js
index 01b93bc42c..44d2e442cb 100644
--- a/test/TooltipSpec.js
+++ b/test/TooltipSpec.js
@@ -5,12 +5,25 @@ import Tooltip from '../src/Tooltip';
describe('Tooltip', function () {
it('Should output a tooltip with content', function () {
let instance = ReactTestUtils.renderIntoDocument(
-
+ Tooltip Content
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
+ const tooltip = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'tooltip');
+ assert.deepEqual(tooltip.props.style, {top: 10, left: 20});
});
+ describe('When a style property is provided', function () {
+ it('Should render a tooltip with merged styles', function () {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+ Tooltip Content
+
+ );
+ const tooltip = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'tooltip');
+ assert.deepEqual(tooltip.props.style, {opacity: 0.9, top: 10, left: 20});
+ });
+ });
});
From 702cb3677e99cd4bf51141ce3fa0dec16db368e3 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Tue, 21 Jul 2015 13:23:40 +0300
Subject: [PATCH 052/128] Update eslint-plugin-react dev dependency
---
.eslintrc | 1 +
package.json | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.eslintrc b/.eslintrc
index cedef098a6..79c77f0d84 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -25,6 +25,7 @@
"quotes": [2, "single", "avoid-escape"],
"react/display-name": 0,
"react/jsx-boolean-value": [2, "never"],
+ "react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
"react/jsx-uses-react": 2,
"react/no-did-mount-set-state": 2,
diff --git a/package.json b/package.json
index c70f241ebb..eb8d97513a 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"eslint": "0.24.1",
"eslint-plugin-babel": "^1.0.0",
"eslint-plugin-mocha": "^0.4.0",
- "eslint-plugin-react": "^2.1.0",
+ "eslint-plugin-react": "^3.0.0",
"express": "^4.12.3",
"extract-text-webpack-plugin": "^0.8.0",
"file-loader": "^0.8.1",
From 5c4a29f6a0ab309c8fd49d797e3f95b1fff63e39 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Tue, 21 Jul 2015 17:15:01 +0300
Subject: [PATCH 053/128] Remove `style` property exposing
---
src/Tooltip.js | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/Tooltip.js b/src/Tooltip.js
index 9d78e6f1ec..1acb42daa4 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -43,11 +43,7 @@ const Tooltip = React.createClass({
/**
* Title text
*/
- title: React.PropTypes.node,
- /**
- * Style hash
- */
- style: React.PropTypes.object
+ title: React.PropTypes.node
},
getDefaultProps() {
@@ -65,7 +61,8 @@ const Tooltip = React.createClass({
const style = {
'left': this.props.positionLeft,
'top': this.props.positionTop,
- ...this.props.style
+ // we don't want to expose the `style` property
+ ...this.props.style // eslint-disable-line react/prop-types
};
const arrowStyle = {
From 3a49843150df1451d57b4415497a66277dbb53f1 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Tue, 21 Jul 2015 10:18:36 -0400
Subject: [PATCH 054/128] fixup! reword docs for boolean fields
---
src/Collapse.js | 8 ++++----
src/Fade.js | 8 ++++----
src/Transition.js | 8 ++++----
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index 30f0cbaa32..ddca971770 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -118,18 +118,18 @@ class Collapse extends React.Component {
Collapse.propTypes = {
/**
- * Whether the component is expanded
+ * Show the component; triggers the expand or collapse animation
*/
in: React.PropTypes.bool,
/**
- * Whether the component should be unmounted (removed from DOM) when
- * collapsed
+ * Unmount the component (remove it from the DOM) when it is collapsed
*/
unmountOnExit: React.PropTypes.bool,
/**
- * Whether the component should expand after mounting
+ * Run the expand animation when the component mounts, if it is initially
+ * shown
*/
transitionAppear: React.PropTypes.bool,
diff --git a/src/Fade.js b/src/Fade.js
index 9e8df40b19..ce5a9a3370 100644
--- a/src/Fade.js
+++ b/src/Fade.js
@@ -21,18 +21,18 @@ class Fade extends React.Component {
Fade.propTypes = {
/**
- * Whether the component is faded in
+ * Show the component; triggers the fade in or fade out animation
*/
in: React.PropTypes.bool,
/**
- * Whether the component should be unmounted (removed from DOM) when faded
- * out
+ * Unmount the component (remove it from the DOM) when it is faded out
*/
unmountOnExit: React.PropTypes.bool,
/**
- * Whether the component should fade in after mounting
+ * Run the fade in animation when the component mounts, if it is initially
+ * shown
*/
transitionAppear: React.PropTypes.bool,
diff --git a/src/Transition.js b/src/Transition.js
index f1c557f4ed..8235d41f9f 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -187,18 +187,18 @@ class Transition extends React.Component {
Transition.propTypes = {
/**
- * Whether the component is shown; triggers the enter or exit animation
+ * Show the component; triggers the enter or exit animation
*/
in: React.PropTypes.bool,
/**
- * Whether the component should be unmounted (removed from DOM) when exited
+ * Unmount the component (remove it from the DOM) when it is not shown
*/
unmountOnExit: React.PropTypes.bool,
/**
- * Whether transition in should run when the Transition component mounts, if
- * the component is initially shown
+ * Run the enter animation when the component mounts, if it is initially
+ * shown
*/
transitionAppear: React.PropTypes.bool,
From 868732d3f72ce3095671dc7a45e13b8de8507660 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Tue, 21 Jul 2015 10:19:52 -0400
Subject: [PATCH 055/128] Fix description of Modal keyboard prop
Fixes #1030
---
src/Modal.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Modal.js b/src/Modal.js
index c42f7b2e0c..417e53fda4 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -104,7 +104,7 @@ const ModalMarkup = React.createClass({
*/
backdrop: React.PropTypes.oneOf(['static', true, false]),
/**
- * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked.
+ * Close the modal when escape key is pressed
*/
keyboard: React.PropTypes.bool,
From 6e194ead95f3f7a55c53a138acc3066f86969db0 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Tue, 21 Jul 2015 11:36:51 -0400
Subject: [PATCH 056/128] fixup! fix target change handler
---
src/Position.js | 33 +++++++++-------
test/PositionSpec.js | 90 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+), 13 deletions(-)
diff --git a/src/Position.js b/src/Position.js
index cb4408e041..c717d288ea 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -13,20 +13,24 @@ class Position extends React.Component {
arrowOffsetLeft: null,
arrowOffsetTop: null
};
+
+ this._needsFlush = false;
}
componentDidMount() {
- this.updatePosition(this.props, this.getTargetSafe(this.props));
+ this.updatePosition();
}
componentWillReceiveProps(nextProps) {
if (nextProps.target !== this.props.target) {
- const target = this.getTargetSafe(this.props);
- const nextTarget = this.getTargetSafe(nextProps);
+ this._needsFlush = true;
+ }
+ }
- if (nextTarget !== target) {
- this.updatePosition(nextProps, nextTarget);
- }
+ componentDidUpdate() {
+ if (this._needsFlush) {
+ this._needsFlush = false;
+ this.updatePosition();
}
}
@@ -51,15 +55,17 @@ class Position extends React.Component {
);
}
- getTargetSafe(props) {
- if (!props.target) {
+ getTargetSafe() {
+ if (!this.props.target) {
return null;
}
- return props.target(props);
+ return this.props.target(this.props);
}
- updatePosition(props, target) {
+ updatePosition() {
+ const target = this.getTargetSafe();
+
if (!target) {
this.setState({
positionLeft: null,
@@ -73,14 +79,15 @@ class Position extends React.Component {
const overlay = React.findDOMNode(this);
const container =
- React.findDOMNode(props.container) || domUtils.ownerDocument(this).body;
+ React.findDOMNode(this.props.container) ||
+ domUtils.ownerDocument(this).body;
this.setState(calcOverlayPosition(
- props.placement,
+ this.props.placement,
overlay,
target,
container,
- props.containerPadding
+ this.props.containerPadding
));
}
}
diff --git a/test/PositionSpec.js b/test/PositionSpec.js
index 963b2c24b7..f157e1e0ee 100644
--- a/test/PositionSpec.js
+++ b/test/PositionSpec.js
@@ -1,6 +1,7 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import Position from '../src/Position';
+import overlayPositionUtils from '../src/utils/overlayPositionUtils';
describe('Position', function () {
it('Should output a child', function () {
@@ -23,5 +24,94 @@ describe('Position', function () {
}).to.throw(Error, /onlyChild must be passed a children with exactly one child/);
});
+ describe('position recalculation', function () {
+ beforeEach(function () {
+ sinon.spy(overlayPositionUtils, 'calcOverlayPosition');
+ sinon.spy(Position.prototype, 'render');
+ });
+
+ afterEach(function () {
+ overlayPositionUtils.calcOverlayPosition.restore();
+ Position.prototype.render.restore();
+ });
+
+ it('should recalculate when target changes', function () {
+ class TargetChanger extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {target: 'foo'};
+ }
+
+ render() {
+ return (
+
{ animation
? {backdrop}
: backdrop
@@ -224,19 +218,49 @@ const ModalMarkup = React.createClass({
);
},
- iosClickHack() {
- // IOS only allows click events to be delegated to the document on elements
- // it considers 'clickable' - anchors, buttons, etc. We fake a click handler on the
- // DOM nodes themselves. Remove if handled by React: https://github.com/facebook/react/issues/1169
- React.findDOMNode(this.refs.modal).onclick = function () {};
- React.findDOMNode(this.refs.backdrop).onclick = function () {};
+ _setDialogRef(ref){
+ this.refs.dialog = ref;
+
+ //maintains backwards compat with older component breakdown
+ if (!this.props.backdrop) {
+ this.refs.modal = ref;
+ }
+ },
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.show) {
+ this.setState({exited: false});
+ } else if (!nextProps.animation) {
+ // Otherwise let handleHidden take care of marking exited.
+ this.setState({exited: true});
+ }
},
- componentWillMount(){
- this.checkForFocus();
+ componentWillUpdate(nextProps){
+ if (nextProps.show) {
+ this.checkForFocus();
+ }
},
componentDidMount() {
+ if ( this.props.show ){
+ this.onShow();
+ }
+ },
+
+ componentDidUpdate(prevProps) {
+ let { animation } = this.props;
+
+ if ( prevProps.show && !this.props.show && !animation) {
+ //otherwise handleHidden will call this.
+ this.onHide();
+ }
+ else if ( !prevProps.show && this.props.show ) {
+ this.onShow();
+ }
+ },
+
+ onShow() {
const doc = domUtils.ownerDocument(this);
const win = domUtils.ownerWindow(this);
@@ -244,7 +268,7 @@ const ModalMarkup = React.createClass({
EventListener.listen(doc, 'keyup', this.handleDocumentKeyUp);
this._onWindowResizeListener =
- EventListener.listen(win, 'resize', this.handleWindowResize);
+ EventListener.listen(win, 'resize', this.handleWindowResize);
if (this.props.enforceFocus) {
this._onFocusinListener = onFocus(this, this.enforceFocus);
@@ -270,19 +294,7 @@ const ModalMarkup = React.createClass({
, () => this.focusModalContent());
},
- componentDidUpdate(prevProps) {
- if (this.props.backdrop && this.props.backdrop !== prevProps.backdrop) {
- this.iosClickHack();
- this.setState(this._getStyles()); //eslint-disable-line react/no-did-update-set-state
- }
-
- if (this.props.container !== prevProps.container) {
- let container = getContainer(this);
- this._containerIsOverflowing = container.scrollHeight > containerClientHeight(container, this);
- }
- },
-
- componentWillUnmount() {
+ onHide() {
this._onDocumentKeyupListener.remove();
this._onWindowResizeListener.remove();
@@ -299,6 +311,13 @@ const ModalMarkup = React.createClass({
this.restoreLastFocus();
},
+ handleHidden(...args) {
+ this.setState({ exited: true });
+
+ this.onHide();
+
+ },
+
handleBackdropClick(e) {
if (e.target !== e.currentTarget) {
return;
@@ -327,7 +346,7 @@ const ModalMarkup = React.createClass({
},
focusModalContent () {
- let modalContent = React.findDOMNode(this.refs.modal);
+ let modalContent = React.findDOMNode(this.refs.dialog);
let current = domUtils.activeElement(this);
let focusInModal = current && domUtils.contains(modalContent, current);
@@ -351,13 +370,21 @@ const ModalMarkup = React.createClass({
}
let active = domUtils.activeElement(this);
- let modal = React.findDOMNode(this.refs.modal);
+ let modal = React.findDOMNode(this.refs.dialog);
if (modal !== active && !domUtils.contains(modal, active)){
modal.focus();
}
},
+ iosClickHack() {
+ // IOS only allows click events to be delegated to the document on elements
+ // it considers 'clickable' - anchors, buttons, etc. We fake a click handler on the
+ // DOM nodes themselves. Remove if handled by React: https://github.com/facebook/react/issues/1169
+ React.findDOMNode(this.refs.modal).onclick = function () {};
+ React.findDOMNode(this.refs.backdrop).onclick = function () {};
+ },
+
_getStyles() {
if ( !domUtils.canUseDom ) { return {}; }
@@ -374,51 +401,7 @@ const ModalMarkup = React.createClass({
}
};
}
-});
-
-const Modal = React.createClass({
- propTypes: {
- ...Portal.propTypes,
- ...ModalMarkup.propTypes
- },
- getDefaultProps(){
- return {
- show: false,
- animation: true
- };
- },
-
- render() {
- let { children, ...props } = this.props;
-
- let show = !!props.show;
-
- let modal = (
-
- { children }
-
- );
-
- return (
-
- { props.animation
- ? (
-
- { modal }
-
- )
- : show && modal
- }
-
-
- );
- }
});
Modal.Body = Body;
@@ -426,6 +409,8 @@ Modal.Header = Header;
Modal.Title = Title;
Modal.Footer = Footer;
+Modal.Dialog = Dialog;
+
Modal.TRANSITION_DURATION = 300;
Modal.BACKDROP_TRANSITION_DURATION = 150;
diff --git a/src/ModalDialog.js b/src/ModalDialog.js
new file mode 100644
index 0000000000..08552f6802
--- /dev/null
+++ b/src/ModalDialog.js
@@ -0,0 +1,59 @@
+/*eslint-disable react/prop-types */
+import React from 'react';
+import classNames from 'classnames';
+import BootstrapMixin from './BootstrapMixin';
+
+const ModalDialog = React.createClass({
+
+ mixins: [ BootstrapMixin ],
+
+ propTypes: {
+
+ /**
+ * A Callback fired when the header closeButton or non-static backdrop is clicked.
+ * @type {function}
+ * @required
+ */
+ onHide: React.PropTypes.func.isRequired,
+
+ /**
+ * A css class to apply to the Modal dialog DOM node.
+ */
+ dialogClassName: React.PropTypes.string
+
+ },
+
+ getDefaultProps() {
+ return {
+ bsClass: 'modal',
+ closeButton: true
+ };
+ },
+
+ render() {
+ let modalStyle = { display: 'block'};
+ let dialogClasses = this.getBsClassSet();
+
+ delete dialogClasses.modal;
+ dialogClasses['modal-dialog'] = true;
+
+ return (
+
+
+
+ { this.props.children }
+
+
+
+ );
+ }
+});
+
+export default ModalDialog;
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 66bb5be9e2..70f0000583 100644
--- a/test/ModalSpec.js
+++ b/test/ModalSpec.js
@@ -52,6 +52,7 @@ describe('Modal', function () {
);
}
});
+
let instance = render(
, mountPoint);
@@ -60,7 +61,7 @@ describe('Modal', function () {
assert.ok(React.findDOMNode(instance).className.match(/\bmodal-open\b/));
- let backdrop = React.findDOMNode(modal.refs.modal).getElementsByClassName('modal-backdrop')[0];
+ let backdrop = React.findDOMNode(modal.refs.backdrop);
ReactTestUtils.Simulate.click(backdrop);
@@ -74,18 +75,17 @@ describe('Modal', function () {
it('Should close the modal when the backdrop is clicked', function (done) {
let doneOp = function () { done(); };
let instance = render(
-
+ Message
, mountPoint);
- let backdrop = React.findDOMNode(instance.refs.modal)
- .getElementsByClassName('modal-backdrop')[0];
+ let backdrop = React.findDOMNode(instance.refs.backdrop);
ReactTestUtils.Simulate.click(backdrop);
});
- it('Should close the modal when the modal background is clicked', function (done) {
+ it('Should close the modal when the modal dialog is clicked', function (done) {
let doneOp = function () { done(); };
let instance = render(
@@ -94,10 +94,9 @@ describe('Modal', function () {
, mountPoint);
- let backdrop = React.findDOMNode(instance.refs.modal)
- .getElementsByClassName('modal')[0];
+ let dialog = React.findDOMNode(instance.refs.dialog);
- ReactTestUtils.Simulate.click(backdrop);
+ ReactTestUtils.Simulate.click(dialog);
});
it('Should close the modal when the modal close button is clicked', function (done) {
@@ -140,6 +139,32 @@ describe('Modal', function () {
assert.match(dialog.props.className, /\btestCss\b/);
});
+ it('Should pass transition callbacks to Transition', function (done) {
+ let count = 0;
+ let increment = ()=> count++;
+
+ let instance = render(
+ {}}
+ onExit={increment}
+ onExiting={increment}
+ onExited={()=> {
+ increment();
+ expect(count).to.equal(6);
+ done();
+ }}
+ onEnter={increment}
+ onEntering={increment}
+ onEntered={()=> {
+ increment();
+ instance.setProps({ show: false });
+ }}
+ >
+ Message
+
+ , mountPoint);
+ });
+
describe('Focused state', function () {
let focusableContainer = null;
From 58eaab04481ef2be56a95d91393637de3471f564 Mon Sep 17 00:00:00 2001
From: jquense
Date: Wed, 22 Jul 2015 20:40:39 -0400
Subject: [PATCH 063/128] [changed] pass transition callbacks to Modal
Transition
---
src/Modal.js | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/Modal.js b/src/Modal.js
index b7744aedb0..f6c64ec3d3 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -141,6 +141,7 @@ const Modal = React.createClass({
render() {
let { children, animation, backdrop, ...props } = this.props;
+ let { onExit, onExiting, onEnter, onEntering, onEntered } = props;
let show = !!props.show;
@@ -166,7 +167,12 @@ const Modal = React.createClass({
unmountOnExit
in={show}
duration={Modal.TRANSITION_DURATION}
+ onExit={onExit}
+ onExiting={onExiting}
onExited={this.handleHidden}
+ onEnter={onEnter}
+ onEntering={onEntering}
+ onEntered={onEntered}
>
{ modal }
@@ -316,6 +322,9 @@ const Modal = React.createClass({
this.onHide();
+ if (this.props.onExited) {
+ this.props.onExited(...args);
+ }
},
handleBackdropClick(e) {
From 86419a069d5aadc031743e95fa944915ff41bd5f Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Thu, 23 Jul 2015 12:25:43 +0300
Subject: [PATCH 064/128] Enable back 'space-infix-ops' eslint rule
---
.eslintrc | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.eslintrc b/.eslintrc
index 79c77f0d84..1326e6f2f4 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -36,8 +36,7 @@
"react/self-closing-comp": 2,
"react/wrap-multilines": 2,
"react/jsx-uses-vars": 2,
- /* disable till https://github.com/eslint/eslint/issues/3016 is resolved */
- "space-infix-ops": 0,
+ "space-infix-ops": 2,
"strict": [2, "never"]
}
}
From 206a0a308a67dd62334cc47ec4e70d69e5fe490f Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Sun, 19 Jul 2015 20:55:22 +0300
Subject: [PATCH 065/128] Remove unnecessary 'eslint' comments
---
src/Collapse.js | 2 +-
src/Input.js | 2 +-
src/NavItem.js | 2 +-
src/Overlay.js | 4 +++-
src/PaginationButton.js | 2 +-
src/Popover.js | 1 -
src/Portal.js | 1 -
src/Tooltip.js | 1 -
src/utils/overlayPositionUtils.js | 2 +-
9 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index ddca971770..2a632f55e9 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -7,7 +7,7 @@ let capitalize = str => str[0].toUpperCase() + str.substr(1);
// reading a dimension prop will cause the browser to recalculate,
// which will let our animations work
-let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+let triggerBrowserReflow = node => node.offsetHeight;
const MARGINS = {
height: ['marginTop', 'marginBottom'],
diff --git a/src/Input.js b/src/Input.js
index f4f5dfe2ab..800ab579bc 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -5,7 +5,7 @@ import deprecationWarning from './utils/deprecationWarning';
class Input extends InputBase {
render() {
- if (this.props.type === 'static') { // eslint-disable-line react/prop-types
+ if (this.props.type === 'static') {
deprecationWarning('Input type=static', 'StaticText');
return ;
}
diff --git a/src/NavItem.js b/src/NavItem.js
index 2be7501eee..42d36bdf2e 100644
--- a/src/NavItem.js
+++ b/src/NavItem.js
@@ -29,7 +29,7 @@ const NavItem = React.createClass({
title,
target,
children,
- 'aria-controls': ariaControls, // eslint-disable-line react/prop-types
+ 'aria-controls': ariaControls,
...props } = this.props;
let classes = {
active,
diff --git a/src/Overlay.js b/src/Overlay.js
index 6d82a7f401..45337814c4 100644
--- a/src/Overlay.js
+++ b/src/Overlay.js
@@ -1,4 +1,6 @@
-/*eslint-disable object-shorthand, react/prop-types */
+/* eslint react/prop-types: [2, {ignore: ["container", "containerPadding", "target", "placement", "children"] }] */
+/* These properties are validated in 'Portal' and 'Position' components */
+
import React, { cloneElement } from 'react';
import Portal from './Portal';
import Position from './Position';
diff --git a/src/PaginationButton.js b/src/PaginationButton.js
index e49e1344ca..23177025be 100644
--- a/src/PaginationButton.js
+++ b/src/PaginationButton.js
@@ -41,7 +41,7 @@ const PaginationButton = React.createClass({
let {
className,
- ...anchorProps // eslint-disable-line object-shorthand
+ ...anchorProps
} = this.props;
return (
diff --git a/src/Popover.js b/src/Popover.js
index 5b9f974d2e..83b88029b0 100644
--- a/src/Popover.js
+++ b/src/Popover.js
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-multi-comp */
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
diff --git a/src/Portal.js b/src/Portal.js
index 4cccd243e7..1de5fd6da7 100644
--- a/src/Portal.js
+++ b/src/Portal.js
@@ -1,4 +1,3 @@
-/*eslint-disable react/prop-types */
import React from 'react';
import CustomPropTypes from './utils/CustomPropTypes';
import domUtils from './utils/domUtils';
diff --git a/src/Tooltip.js b/src/Tooltip.js
index 1acb42daa4..b500244c37 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-multi-comp */
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
diff --git a/src/utils/overlayPositionUtils.js b/src/utils/overlayPositionUtils.js
index eb4a0459ec..050b68fdff 100644
--- a/src/utils/overlayPositionUtils.js
+++ b/src/utils/overlayPositionUtils.js
@@ -25,7 +25,7 @@ const utils = {
domUtils.getOffset(target) : domUtils.getPosition(target, container);
return {
- ...offset, // eslint-disable-line object-shorthand
+ ...offset,
height: target.offsetHeight,
width: target.offsetWidth
};
From 86d3febe2f16d5e70c00e63b2fd37014fbb14a8f Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Sun, 19 Jul 2015 20:56:26 +0300
Subject: [PATCH 066/128] [fixed] added missed 'aria-label' prop type
validation for 'ModalHeader'
and doc description for it
---
src/ModalHeader.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/ModalHeader.js b/src/ModalHeader.js
index 0ab2dda5cf..8a54cce91a 100644
--- a/src/ModalHeader.js
+++ b/src/ModalHeader.js
@@ -13,7 +13,6 @@ class ModalHeader extends React.Component {
{ this.props.closeButton &&
@@ -31,6 +30,11 @@ class ModalHeader extends React.Component {
ModalHeader.__isModalHeader = true;
ModalHeader.propTypes = {
+ /**
+ * The 'aria-label' attribute is used to define a string that labels the current element.
+ * It is used for Assistive Technology when the label text is not visible on screen.
+ */
+ 'aria-label': React.PropTypes.string,
/**
* A css class applied to the Component
*/
@@ -47,6 +51,7 @@ ModalHeader.propTypes = {
};
ModalHeader.defaultProps = {
+ 'aria-label': 'Close',
modalClassName: 'modal-header',
closeButton: false
};
From e8ef76a25b1489702e11596ed0a48d5fa850d4a6 Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Thu, 23 Jul 2015 13:12:26 +0300
Subject: [PATCH 067/128] Remove dead example
---
docs/examples/CollapsibleParagraph.js | 37 ---------------------------
docs/src/Samples.js | 1 -
2 files changed, 38 deletions(-)
delete mode 100644 docs/examples/CollapsibleParagraph.js
diff --git a/docs/examples/CollapsibleParagraph.js b/docs/examples/CollapsibleParagraph.js
deleted file mode 100644
index e44ca2e628..0000000000
--- a/docs/examples/CollapsibleParagraph.js
+++ /dev/null
@@ -1,37 +0,0 @@
-const CollapsibleParagraph = React.createClass({
- mixins: [CollapsibleMixin],
-
- getCollapsibleDOMNode(){
- return React.findDOMNode(this.refs.panel);
- },
-
- getCollapsibleDimensionValue(){
- return React.findDOMNode(this.refs.panel).scrollHeight;
- },
-
- onHandleToggle(e){
- e.preventDefault();
- this.setState({expanded:!this.state.expanded});
- },
-
- render(){
- let styles = this.getCollapsibleClassSet();
- let text = this.isExpanded() ? 'Hide' : 'Show';
- return (
-
- {text} Content
-
- {this.props.children}
-
-
- );
- }
-});
-
-const panelInstance = (
-
- Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
-
-);
-
-React.render(panelInstance, mountNode);
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index 67b1580bd8..cd02db6000 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -32,7 +32,6 @@ export default {
PanelGroupControlled: require('fs').readFileSync(__dirname + '/../examples/PanelGroupControlled.js', 'utf8'),
PanelGroupUncontrolled: require('fs').readFileSync(__dirname + '/../examples/PanelGroupUncontrolled.js', 'utf8'),
PanelGroupAccordion: require('fs').readFileSync(__dirname + '/../examples/PanelGroupAccordion.js', 'utf8'),
- CollapsibleParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsibleParagraph.js', 'utf8'),
Modal: require('fs').readFileSync(__dirname + '/../examples/Modal.js', 'utf8'),
ModalContained: require('fs').readFileSync(__dirname + '/../examples/ModalContained.js', 'utf8'),
ModalDefaultSizing: require('fs').readFileSync(__dirname + '/../examples/ModalDefaultSizing.js', 'utf8'),
From 0944cddda8c5707e454a2afb054f8b20f102380d Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Thu, 23 Jul 2015 13:07:24 +0300
Subject: [PATCH 068/128] Add standalone collapsible Panel example
---
docs/examples/PanelCollapsible.js | 24 ++++++++++++++++++++++++
docs/src/ComponentsPage.js | 3 +++
docs/src/Samples.js | 1 +
3 files changed, 28 insertions(+)
create mode 100644 docs/examples/PanelCollapsible.js
diff --git a/docs/examples/PanelCollapsible.js b/docs/examples/PanelCollapsible.js
new file mode 100644
index 0000000000..51d41a1fee
--- /dev/null
+++ b/docs/examples/PanelCollapsible.js
@@ -0,0 +1,24 @@
+class Example extends React.Component {
+ constructor(...args){
+ super(...args);
+ this.state = {
+ open: true
+ };
+ }
+
+ render(){
+ return (
+
+ this.setState({ open: !this.state.open })}>
+ click
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
+ Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
+
+
You can pass on any additional properties you need, e.g. a custom onClick handler, as it is shown in the example code. They all will apply to the wrapper div element.
+
Collapsible Panel
+
+
Panel with heading
Easily add a heading container to your panel with the header prop.
From 95ee7a17b2b38dde6c6cf676ccb31e0672f26c29 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Wed, 5 Aug 2015 07:53:54 -0600
Subject: [PATCH 114/128] Remove reference to factories from documentation
---
docs/src/GettingStartedPage.js | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/docs/src/GettingStartedPage.js b/docs/src/GettingStartedPage.js
index 5fdcd7f112..8fd2a3a50b 100644
--- a/docs/src/GettingStartedPage.js
+++ b/docs/src/GettingStartedPage.js
@@ -85,18 +85,6 @@ $ bower install react-bootstrap`
/>
-
Without JSX
-
If you do not use JSX and just call components as functions, you must explicitly create a factory before calling it. React-bootstrap provides factories for you in lib/factories: