406 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			406 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
import React from 'react';
 | 
						|
import PropTypes from 'prop-types';
 | 
						|
import { findDOMNode } from 'react-dom';
 | 
						|
import classNames from 'classnames';
 | 
						|
import keycode from 'keycode';
 | 
						|
import ownerWindow from 'dom-helpers/ownerWindow';
 | 
						|
import withStyles from '../styles/withStyles';
 | 
						|
import { listenForFocusKeys, detectKeyboardFocus, focusKeyPressed } from '../utils/keyboardFocus';
 | 
						|
import TouchRipple from './TouchRipple';
 | 
						|
import createRippleHandler from './createRippleHandler';
 | 
						|
 | 
						|
export const styles = {
 | 
						|
  root: {
 | 
						|
    display: 'inline-flex',
 | 
						|
    alignItems: 'center',
 | 
						|
    justifyContent: 'center',
 | 
						|
    position: 'relative',
 | 
						|
    // Remove grey highlight
 | 
						|
    WebkitTapHighlightColor: 'transparent',
 | 
						|
    backgroundColor: 'transparent', // Reset default value
 | 
						|
    outline: 'none',
 | 
						|
    border: 0,
 | 
						|
    margin: 0, // Remove the margin in Safari
 | 
						|
    borderRadius: 0,
 | 
						|
    padding: 0, // Remove the padding in Firefox
 | 
						|
    cursor: 'pointer',
 | 
						|
    userSelect: 'none',
 | 
						|
    verticalAlign: 'middle',
 | 
						|
    '-moz-appearance': 'none', // Reset
 | 
						|
    '-webkit-appearance': 'none', // Reset
 | 
						|
    textDecoration: 'none',
 | 
						|
    // So we take precedent over the style of a native <a /> element.
 | 
						|
    color: 'inherit',
 | 
						|
    '&::-moz-focus-inner': {
 | 
						|
      borderStyle: 'none', // Remove Firefox dotted outline.
 | 
						|
    },
 | 
						|
  },
 | 
						|
  disabled: {
 | 
						|
    pointerEvents: 'none', // Disable link interactions
 | 
						|
    cursor: 'default',
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * `ButtonBase` contains as few styles as possible.
 | 
						|
 * It aims to be a simple building block for creating a button.
 | 
						|
 * It contains a load of style reset and some focus/ripple logic.
 | 
						|
 */
 | 
						|
class ButtonBase extends React.Component {
 | 
						|
  state = {
 | 
						|
    keyboardFocused: false,
 | 
						|
  };
 | 
						|
 | 
						|
  componentDidMount() {
 | 
						|
    this.button = findDOMNode(this);
 | 
						|
    listenForFocusKeys(ownerWindow(this.button));
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillReceiveProps(nextProps) {
 | 
						|
    // The blur won't fire when the disabled state is set on a focused input.
 | 
						|
    // We need to book keep the focused state manually.
 | 
						|
    if (!this.props.disabled && nextProps.disabled && this.state.keyboardFocused) {
 | 
						|
      this.setState({
 | 
						|
        keyboardFocused: false,
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillUpdate(nextProps, nextState) {
 | 
						|
    if (
 | 
						|
      this.props.focusRipple &&
 | 
						|
      nextState.keyboardFocused &&
 | 
						|
      !this.state.keyboardFocused &&
 | 
						|
      !this.props.disableRipple
 | 
						|
    ) {
 | 
						|
      this.ripple.pulsate();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillUnmount() {
 | 
						|
    this.button = null;
 | 
						|
    clearTimeout(this.keyboardFocusTimeout);
 | 
						|
  }
 | 
						|
 | 
						|
  onKeyboardFocusHandler = event => {
 | 
						|
    this.keyDown = false;
 | 
						|
    this.setState({ keyboardFocused: true });
 | 
						|
 | 
						|
    if (this.props.onKeyboardFocus) {
 | 
						|
      this.props.onKeyboardFocus(event);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  onRippleRef = node => {
 | 
						|
    this.ripple = node;
 | 
						|
  };
 | 
						|
 | 
						|
  ripple = null;
 | 
						|
  keyDown = false; // Used to help track keyboard activation keyDown
 | 
						|
  button = null;
 | 
						|
  keyboardFocusTimeout = null;
 | 
						|
  keyboardFocusCheckTime = 50;
 | 
						|
  keyboardFocusMaxCheckTimes = 5;
 | 
						|
 | 
						|
  handleKeyDown = event => {
 | 
						|
    const { component, focusRipple, onKeyDown, onClick } = this.props;
 | 
						|
    const key = keycode(event);
 | 
						|
 | 
						|
    // Check if key is already down to avoid repeats being counted as multiple activations
 | 
						|
    if (
 | 
						|
      focusRipple &&
 | 
						|
      !this.keyDown &&
 | 
						|
      this.state.keyboardFocused &&
 | 
						|
      this.ripple &&
 | 
						|
      key === 'space'
 | 
						|
    ) {
 | 
						|
      this.keyDown = true;
 | 
						|
      event.persist();
 | 
						|
      this.ripple.stop(event, () => {
 | 
						|
        this.ripple.start(event);
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    if (onKeyDown) {
 | 
						|
      onKeyDown(event);
 | 
						|
    }
 | 
						|
 | 
						|
    // Keyboard accessibility for non interactive elements
 | 
						|
    if (
 | 
						|
      event.target === event.currentTarget &&
 | 
						|
      component &&
 | 
						|
      component !== 'button' &&
 | 
						|
      (key === 'space' || key === 'enter')
 | 
						|
    ) {
 | 
						|
      event.preventDefault();
 | 
						|
      if (onClick) {
 | 
						|
        onClick(event);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  handleKeyUp = event => {
 | 
						|
    if (
 | 
						|
      this.props.focusRipple &&
 | 
						|
      keycode(event) === 'space' &&
 | 
						|
      this.ripple &&
 | 
						|
      this.state.keyboardFocused
 | 
						|
    ) {
 | 
						|
      this.keyDown = false;
 | 
						|
      event.persist();
 | 
						|
      this.ripple.stop(event, () => this.ripple.pulsate(event));
 | 
						|
    }
 | 
						|
    if (this.props.onKeyUp) {
 | 
						|
      this.props.onKeyUp(event);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  handleMouseDown = createRippleHandler(this, 'MouseDown', 'start', () => {
 | 
						|
    clearTimeout(this.keyboardFocusTimeout);
 | 
						|
    focusKeyPressed(false);
 | 
						|
    if (this.state.keyboardFocused) {
 | 
						|
      this.setState({ keyboardFocused: false });
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  handleMouseUp = createRippleHandler(this, 'MouseUp', 'stop');
 | 
						|
 | 
						|
  handleMouseLeave = createRippleHandler(this, 'MouseLeave', 'stop', event => {
 | 
						|
    if (this.state.keyboardFocused) {
 | 
						|
      event.preventDefault();
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  handleTouchStart = createRippleHandler(this, 'TouchStart', 'start');
 | 
						|
 | 
						|
  handleTouchEnd = createRippleHandler(this, 'TouchEnd', 'stop');
 | 
						|
 | 
						|
  handleTouchMove = createRippleHandler(this, 'TouchEnd', 'stop');
 | 
						|
 | 
						|
  handleBlur = createRippleHandler(this, 'Blur', 'stop', () => {
 | 
						|
    clearTimeout(this.keyboardFocusTimeout);
 | 
						|
    focusKeyPressed(false);
 | 
						|
    this.setState({ keyboardFocused: false });
 | 
						|
  });
 | 
						|
 | 
						|
  handleFocus = event => {
 | 
						|
    if (this.props.disabled) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Fix for https://github.com/facebook/react/issues/7769
 | 
						|
    if (!this.button) {
 | 
						|
      this.button = event.currentTarget;
 | 
						|
    }
 | 
						|
 | 
						|
    event.persist();
 | 
						|
    detectKeyboardFocus(this, this.button, () => {
 | 
						|
      this.onKeyboardFocusHandler(event);
 | 
						|
    });
 | 
						|
 | 
						|
    if (this.props.onFocus) {
 | 
						|
      this.props.onFocus(event);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  render() {
 | 
						|
    const {
 | 
						|
      buttonRef,
 | 
						|
      centerRipple,
 | 
						|
      children,
 | 
						|
      classes,
 | 
						|
      className: classNameProp,
 | 
						|
      component,
 | 
						|
      disabled,
 | 
						|
      disableRipple,
 | 
						|
      focusRipple,
 | 
						|
      keyboardFocusedClassName,
 | 
						|
      onBlur,
 | 
						|
      onFocus,
 | 
						|
      onKeyboardFocus,
 | 
						|
      onKeyDown,
 | 
						|
      onKeyUp,
 | 
						|
      onMouseDown,
 | 
						|
      onMouseLeave,
 | 
						|
      onMouseUp,
 | 
						|
      onTouchEnd,
 | 
						|
      onTouchMove,
 | 
						|
      onTouchStart,
 | 
						|
      tabIndex,
 | 
						|
      type,
 | 
						|
      ...other
 | 
						|
    } = this.props;
 | 
						|
 | 
						|
    const className = classNames(
 | 
						|
      classes.root,
 | 
						|
      {
 | 
						|
        [classes.disabled]: disabled,
 | 
						|
        [keyboardFocusedClassName || '']: this.state.keyboardFocused,
 | 
						|
      },
 | 
						|
      classNameProp,
 | 
						|
    );
 | 
						|
 | 
						|
    const buttonProps = {};
 | 
						|
 | 
						|
    let ComponentProp = component;
 | 
						|
 | 
						|
    if (!ComponentProp) {
 | 
						|
      if (other.href) {
 | 
						|
        ComponentProp = 'a';
 | 
						|
      } else {
 | 
						|
        ComponentProp = 'button';
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (ComponentProp === 'button') {
 | 
						|
      buttonProps.type = type || 'button';
 | 
						|
      buttonProps.disabled = disabled;
 | 
						|
    } else {
 | 
						|
      buttonProps.role = 'button';
 | 
						|
    }
 | 
						|
 | 
						|
    return (
 | 
						|
      <ComponentProp
 | 
						|
        onBlur={this.handleBlur}
 | 
						|
        onFocus={this.handleFocus}
 | 
						|
        onKeyDown={this.handleKeyDown}
 | 
						|
        onKeyUp={this.handleKeyUp}
 | 
						|
        onMouseDown={this.handleMouseDown}
 | 
						|
        onMouseLeave={this.handleMouseLeave}
 | 
						|
        onMouseUp={this.handleMouseUp}
 | 
						|
        onTouchEnd={this.handleTouchEnd}
 | 
						|
        onTouchMove={this.handleTouchMove}
 | 
						|
        onTouchStart={this.handleTouchStart}
 | 
						|
        tabIndex={disabled ? '-1' : tabIndex}
 | 
						|
        className={className}
 | 
						|
        ref={buttonRef}
 | 
						|
        {...buttonProps}
 | 
						|
        {...other}
 | 
						|
      >
 | 
						|
        {children}
 | 
						|
        {!disableRipple && !disabled ? (
 | 
						|
          <TouchRipple innerRef={this.onRippleRef} center={centerRipple} />
 | 
						|
        ) : null}
 | 
						|
      </ComponentProp>
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
ButtonBase.propTypes = {
 | 
						|
  /**
 | 
						|
   * Use that property to pass a ref callback to the native button component.
 | 
						|
   */
 | 
						|
  buttonRef: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * If `true`, the ripples will be centered.
 | 
						|
   * They won't start at the cursor interaction position.
 | 
						|
   */
 | 
						|
  centerRipple: PropTypes.bool,
 | 
						|
  /**
 | 
						|
   * The content of the component.
 | 
						|
   */
 | 
						|
  children: PropTypes.node,
 | 
						|
  /**
 | 
						|
   * Useful to extend the style applied to components.
 | 
						|
   */
 | 
						|
  classes: PropTypes.object.isRequired,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  className: PropTypes.string,
 | 
						|
  /**
 | 
						|
   * The component used for the root node.
 | 
						|
   * Either a string to use a DOM element or a component.
 | 
						|
   * The default value is a `button`.
 | 
						|
   */
 | 
						|
  component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
 | 
						|
  /**
 | 
						|
   * If `true`, the base button will be disabled.
 | 
						|
   */
 | 
						|
  disabled: PropTypes.bool,
 | 
						|
  /**
 | 
						|
   * If `true`, the ripple effect will be disabled.
 | 
						|
   */
 | 
						|
  disableRipple: PropTypes.bool,
 | 
						|
  /**
 | 
						|
   * If `true`, the base button will have a keyboard focus ripple.
 | 
						|
   * `disableRipple` must also be `false`.
 | 
						|
   */
 | 
						|
  focusRipple: PropTypes.bool,
 | 
						|
  /**
 | 
						|
   * The CSS class applied while the component is keyboard focused.
 | 
						|
   */
 | 
						|
  keyboardFocusedClassName: PropTypes.string,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onBlur: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onClick: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onFocus: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * Callback fired when the component is focused with a keyboard.
 | 
						|
   * We trigger a `onFocus` callback too.
 | 
						|
   */
 | 
						|
  onKeyboardFocus: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onKeyDown: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onKeyUp: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onMouseDown: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onMouseLeave: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onMouseUp: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onTouchEnd: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onTouchMove: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  onTouchStart: PropTypes.func,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  role: PropTypes.string,
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
 | 
						|
  /**
 | 
						|
   * @ignore
 | 
						|
   */
 | 
						|
  type: PropTypes.string,
 | 
						|
};
 | 
						|
 | 
						|
ButtonBase.defaultProps = {
 | 
						|
  centerRipple: false,
 | 
						|
  disableRipple: false,
 | 
						|
  focusRipple: false,
 | 
						|
  tabIndex: '0',
 | 
						|
  type: 'button',
 | 
						|
};
 | 
						|
 | 
						|
export default withStyles(styles, { name: 'MuiButtonBase' })(ButtonBase);
 |