import * as React from 'react'; import * as PropTypes from 'prop-types'; import * as classNames from 'classnames'; import { TabContextType } from './ContextTypes'; export interface TabContentProps { className?: string, componentClass?: React.ReactType, /** * Sets a default animation strategy for all children ``s. Use * `false` to disable, `true` to enable the default `` animation or any * `` component. */ animation?: boolean | React.ReactType, /** * Unmount tabs (remove it from the DOM) when they are no longer visible */ unmountOnExit?: boolean, } export interface TabContentState { activeKey: string | number, activeChild: any } export class TabContent extends React.Component { private isUnmounted = false; private static contextTypes = { $bs_tabContainer: PropTypes.shape({ activeKey: PropTypes.any, }), } private static childContextTypes = { $bs_tabContent: PropTypes.shape({ bsClass: PropTypes.string, animation: PropTypes.oneOfType([ PropTypes.bool, PropTypes.element, ]), activeKey: PropTypes.any, unmountOnExit: PropTypes.bool, onPaneEnter: PropTypes.func.isRequired, onPaneExited: PropTypes.func.isRequired, exiting: PropTypes.bool.isRequired, }), } public static defaultProps: TabContentProps = { componentClass: 'div', animation: true, unmountOnExit: false, } constructor(props: TabContentProps, context: TabContextType) { super(props, context); this.handlePaneEnter = this.handlePaneEnter.bind(this); this.handlePaneExited = this.handlePaneExited.bind(this); // Active entries in state will be `null` unless `animation` is set. Need // to track active child in case keys swap and the active child changes // but the active key does not. this.state = { activeKey: null, activeChild: null, }; } getChildContext(): TabContextType { const { animation, unmountOnExit } = this.props; const stateActiveKey = this.state.activeKey; const containerActiveKey = this.getContainerActiveKey(); const activeKey = stateActiveKey != null ? stateActiveKey : containerActiveKey; const exiting = stateActiveKey != null && stateActiveKey !== containerActiveKey; return { $bs_tabContent: { animation, activeKey, unmountOnExit, onPaneEnter: this.handlePaneEnter, onPaneExited: this.handlePaneExited, exiting, }, }; } componentWillReceiveProps(nextProps) { if (!nextProps.animation && this.state.activeChild) { this.setState({ activeKey: null, activeChild: null }); } } componentWillUnmount() { this.isUnmounted = true; } handlePaneEnter = (child, childKey) => { if (!this.props.animation) { return false; } // It's possible that this child should be transitioning out. if (childKey !== this.getContainerActiveKey()) { return false; } this.setState({ activeKey: childKey, activeChild: child, }); return true; } handlePaneExited = (child) => { // This might happen as everything is unmounting. if (this.isUnmounted) { return; } this.setState(({ activeChild }) => { if (activeChild !== child) { return null; } return { activeKey: null, activeChild: null, }; }); } getContainerActiveKey = () => { const { $bs_tabContainer: tabContainer } = this.context as TabContextType; return tabContainer && tabContainer.activeKey; } render() { const { componentClass: Component, className, ...props } = this.props; const { animation, unmountOnExit, ...otherProps } = props; return ( ); } }