import React, { Component, PropTypes } from 'react'
import _ from "lodash"
import classnames from 'classnames'
import { Key } from '../../utils'
export default class ValueEditor extends Component {
static propTypes = {
content: PropTypes.string.isRequired,
readonly: PropTypes.bool,
onDone: PropTypes.func.isRequired,
className: PropTypes.string,
onInput: PropTypes.func,
onKeyDown: PropTypes.func,
}
static defaultProps = {
onInput: () => {},
onKeyDown: () => {},
}
constructor(props) {
super(props)
this.state = { editable: false }
this.onPaste = this.onPaste.bind(this)
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
this.onFocus = this.onFocus.bind(this)
this.onClick = this.onClick.bind(this)
this.blur = this.blur.bind(this)
this.onBlur = this.onBlur.bind(this)
this.reset = this.reset.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
this.onInput = this.onInput.bind(this)
}
blur() {
// a stop would cause a blur as a side-effect.
// but a blur event must trigger a stop as well.
// to fix this, make stop = blur and do the actual stop in the onBlur handler.
this.input.blur()
}
reset() {
this.input.innerHTML = _.escape(this.props.content)
}
render() {
let className = classnames(
'inline-input',
{
'readonly': this.props.readonly,
'editable': !this.props.readonly
},
this.props.className
)
return (
this.input = input}
tabIndex={this.props.readonly ? undefined : 0}
className={className}
contentEditable={this.state.editable || undefined}
onFocus={this.onFocus}
onMouseDown={this.onMouseDown}
onClick={this.onClick}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onInput={this.onInput}
onPaste={this.onPaste}
dangerouslySetInnerHTML={{ __html: _.escape(this.props.content) }}
>
)
}
onPaste(e) {
e.preventDefault()
var content = e.clipboardData.getData('text/plain')
document.execCommand('insertHTML', false, content)
}
onMouseDown(e) {
this._mouseDown = true
window.addEventListener('mouseup', this.onMouseUp)
}
onMouseUp() {
if (this._mouseDown) {
this._mouseDown = false
window.removeEventListener('mouseup', this.onMouseUp)
}
}
onClick(e) {
this.onMouseUp()
this.onFocus(e)
}
onFocus(e) {
if (this._mouseDown || this._ignore_events || this.state.editable || this.props.readonly) {
return
}
// contenteditable in FireFox is more or less broken.
// - we need to blur() and then focus(), otherwise the caret is not shown.
// - blur() + focus() == we need to save the caret position before
// Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint
const sel = window.getSelection()
let range
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0)
} else if (document.caretPositionFromPoint && e.clientX && e.clientY) {
const pos = document.caretPositionFromPoint(e.clientX, e.clientY)
range = document.createRange()
range.setStart(pos.offsetNode, pos.offset)
} else if (document.caretRangeFromPoint && e.clientX && e.clientY) {
range = document.caretRangeFromPoint(e.clientX, e.clientY)
} else {
range = document.createRange()
range.selectNodeContents(this.input)
}
this._ignore_events = true
this.setState({ editable: true }, () => {
this.input.blur()
this.input.focus()
this._ignore_events = false
range.selectNodeContents(this.input)
sel.removeAllRanges();
sel.addRange(range);
})
}
onBlur(e) {
if (this._ignore_events || this.props.readonly) {
return
}
window.getSelection().removeAllRanges() //make sure that selection is cleared on blur
this.setState({ editable: false })
this.props.onDone(this.input.textContent)
}
onKeyDown(e) {
e.stopPropagation()
switch (e.keyCode) {
case Key.ESC:
e.preventDefault()
this.reset()
this.blur()
break
case Key.ENTER:
if (!e.shiftKey) {
e.preventDefault()
this.blur()
}
break
default:
break
}
this.props.onKeyDown(e)
}
onInput() {
this.props.onInput(this.input.textContent)
}
}