All files / src/components ContentEditable.js

70.83% Statements 17/24
50% Branches 3/6
50% Functions 6/12
70.83% Lines 17/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 831x 1x 1x                     4x 4x 4x     16x                                           1x 1x   1x 1x                   4x 4x 4x                         4x 4x     8x                  
import React, {Component} from 'react';
import PropTypes from 'prop-types/prop-types';
import shallowequal from 'shallowequal';
 
/**
 * Single line contentEditable
 *
 * This component creates a span with contenteditable
 * It only supports one line content, so users are expected
 * to pass in onKeyDown and intercept the enter key
 */
 
function getInnerHTML(txt) {
  const el = document.createElement('div');
  el.textContent = txt;
  return el.innerHTML;
}
 
export default class ContentEditable extends Component {
  static defaultProps = {
    onChange: () => {},
    onKeyDown: () => {},
    itDidMount: () => {},
    value: '',
  }
 
  static propTypes = {
    onChange: PropTypes.func,
    onKeyDown: PropTypes.func,
    itDidMount: PropTypes.func,
    value: PropTypes.string,
    id: PropTypes.string,
    'aria-label': PropTypes.string,
  }
 
  handleChange = _ => {
    this.props.onChange(this.elem.textContent);
  }
 
  handleKeyDown = e => {
    Eif (e.keyCode === 13 && !e.shiftKey) { // ENTER
      e.preventDefault();
    }
    e.stopPropagation();
    this.props.onKeyDown(e);
  }
 
  handlePaste = ev => {
    ev.preventDefault();
    const text = ev.clipboardData.getData('text');
    document.execCommand('insertText', false, text);
  }
 
  componentDidMount() {
    this.props.itDidMount(this.elem);
    this.elem.spellcheck = false;
    this.elem.focus();
  }
 
  shouldComponentUpdate(props) {
    const {value: newValue, 'aria-label': newAriaLabel, ...newProps} = props;
    const {value: oldValue, 'aria-label': oldAriaLabel, ...oldProps} = this.props;
    return (
      (getInnerHTML(newValue) !== this.elem.innerHTML) ||
       !shallowequal(newProps, oldProps)
    );
  }
 
  render () {
    const {value, itDidMount, ...props} = this.props;
    return (
      <span
        {...props}
        ref={elem => this.elem = elem}
        dangerouslySetInnerHTML={{__html: getInnerHTML(value)}}
        contentEditable={true}
        onInput={this.handleChange}
        onKeyDown={this.handleKeyDown}
        onPaste={this.handlePaste} />
    );
  }
}