Fixed losing caret position whilst typing

This commit is contained in:
jos 2016-08-26 12:16:43 +02:00
parent ea6b00b67d
commit 7280239771
4 changed files with 83 additions and 22 deletions

View File

@ -1,4 +1,4 @@
import { h, render, Component } from 'preact' import { h, Component } from 'preact'
export let CONTEXT_MENU_HEIGHT = 240 export let CONTEXT_MENU_HEIGHT = 240

View File

@ -2,7 +2,6 @@ import { h, Component } from 'preact'
import ContextMenu from './ContextMenu' import ContextMenu from './ContextMenu'
import { escapeHTML, unescapeHTML } from './utils/stringUtils' import { escapeHTML, unescapeHTML } from './utils/stringUtils'
import { last } from './utils/arrayUtils'
import { getInnerText } from './utils/domUtils' import { getInnerText } from './utils/domUtils'
import {stringConvert, valueType, isUrl} from './utils/typeUtils' import {stringConvert, valueType, isUrl} from './utils/typeUtils'
@ -18,7 +17,9 @@ const TYPE_TITLES = {
'string': 'Item type "string". ' + 'string': 'Item type "string". ' +
'Item type is not determined from the value, ' + 'Item type is not determined from the value, ' +
'but always returned as string.' 'but always returned as string.'
}; }
const URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url'
/** /**
* @type {JSONNode | null} activeContextMenu singleton holding the JSONNode having * @type {JSONNode | null} activeContextMenu singleton holding the JSONNode having
@ -178,18 +179,18 @@ export default class JSONNode extends Component {
class: 'jsoneditor-property' + (prop.length === 0 ? ' jsoneditor-empty' : ''), class: 'jsoneditor-property' + (prop.length === 0 ? ' jsoneditor-empty' : ''),
contentEditable: 'true', contentEditable: 'true',
spellCheck: 'false', spellCheck: 'false',
onInput: this.handleChangeProperty onBlur: this.handleChangeProperty
}, prop) }, prop)
} }
} }
else { else {
// root node // root node
const content = JSONNode._rootName(data, options) const content = JSONNode._getRootName(data, options)
return h('div', { return h('div', {
class: 'jsoneditor-property jsoneditor-readonly', class: 'jsoneditor-property jsoneditor-readonly',
spellCheck: 'false', spellCheck: 'false',
onInput: this.handleChangeProperty onBlur: this.handleChangeProperty
}, content) }, content)
} }
} }
@ -203,22 +204,73 @@ export default class JSONNode extends Component {
const type = valueType (value) const type = valueType (value)
const _isUrl = isUrl(value) const _isUrl = isUrl(value)
const isEmpty = escapedValue.length === 0 const isEmpty = escapedValue.length === 0
const valueClass = 'jsoneditor-value ' +
'jsoneditor-' + type +
(_isUrl ? ' jsoneditor-url' : '') +
(isEmpty ? ' jsoneditor-empty' : '')
return h('div', { return h('div', {
class: valueClass, class: JSONNode._getValueClass(type, _isUrl, isEmpty),
contentEditable: 'true', contentEditable: 'true',
spellCheck: 'false', spellCheck: 'false',
onInput: this.handleChangeValue, onBlur: this.handleChangeValue,
onInput: this.updateValueStyling,
onClick: this.handleClickValue, onClick: this.handleClickValue,
onKeyDown: this.handleKeyDownValue, onKeyDown: this.handleKeyDownValue,
title: _isUrl ? 'Ctrl+Click or ctrl+Enter to open url' : null title: _isUrl ? URL_TITLE : null
}, escapedValue) }, escapedValue)
} }
/**
* Note: this function manipulates the className and title of the editable div
* outside of Preact, so the user gets immediate feedback
* @param event
*/
updateValueStyling = (event) => {
const value = this._getValueFromEvent(event)
const type = valueType (value)
const _isUrl = isUrl(value)
const isEmpty = false // not needed, our div has a border and is clearly visible
// find the editable div, the root
let target = event.target
while (target.contentEditable !== 'true') {
target = target.parentNode
}
target.className = JSONNode._getValueClass(type, _isUrl, isEmpty)
target.title = _isUrl ? URL_TITLE : ''
// remove all classNames from childs (needed for IE and Edge)
JSONNode._removeChildClasses(target)
}
/**
* Create the className for the property value
* @param {string} type
* @param {boolean} isUrl
* @param {boolean} isEmpty
* @return {string}
* @private
*/
static _getValueClass (type, isUrl, isEmpty) {
return 'jsoneditor-value ' +
'jsoneditor-' + type +
(isUrl ? ' jsoneditor-url' : '') +
(isEmpty ? ' jsoneditor-empty' : '')
}
/**
* Recursively remove all classes from the childs of this element
* @param elem
* @private
*/
static _removeChildClasses (elem) {
for (let i = 0; i < elem.childNodes.length; i++) {
const child = elem.childNodes[i]
if (child.class) {
child.class = ''
}
JSONNode._removeChildClasses(child)
}
}
renderExpandButton () { renderExpandButton () {
const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}` const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}`
return h('div', {class: 'jsoneditor-button-container'}, return h('div', {class: 'jsoneditor-button-container'},
@ -294,10 +346,10 @@ export default class JSONNode extends Component {
click: () => events.onChangeType(path, 'string') click: () => events.onChangeType(path, 'string')
} }
] ]
}); })
if (type === 'array' || type === 'object') { if (type === 'array' || type === 'object') {
var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc'); var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc')
items.push({ items.push({
text: 'Sort', text: 'Sort',
title: 'Sort the childs of this ' + TYPE_TITLES.type, title: 'Sort the childs of this ' + TYPE_TITLES.type,
@ -317,7 +369,7 @@ export default class JSONNode extends Component {
click: () => events.onSort(path, 'desc') click: () => events.onSort(path, 'desc')
} }
] ]
}); })
} }
if (hasParent) { if (hasParent) {
@ -328,7 +380,7 @@ export default class JSONNode extends Component {
// create a separator // create a separator
items.push({ items.push({
'type': 'separator' 'type': 'separator'
}); })
} }
// create insert button // create insert button
@ -364,7 +416,7 @@ export default class JSONNode extends Component {
click: () => events.onInsert(parentPath, prop, 'string') click: () => events.onInsert(parentPath, prop, 'string')
} }
] ]
}); })
// create duplicate button // create duplicate button
items.push({ items.push({
@ -372,7 +424,7 @@ export default class JSONNode extends Component {
title: 'Duplicate this item (Ctrl+D)', title: 'Duplicate this item (Ctrl+D)',
className: 'jsoneditor-duplicate', className: 'jsoneditor-duplicate',
click: () => events.onDuplicate(parentPath, prop) click: () => events.onDuplicate(parentPath, prop)
}); })
// create remove button // create remove button
items.push({ items.push({
@ -380,7 +432,7 @@ export default class JSONNode extends Component {
title: 'Remove this item (Ctrl+Del)', title: 'Remove this item (Ctrl+Del)',
className: 'jsoneditor-remove', className: 'jsoneditor-remove',
click: () => events.onRemove(parentPath, prop) click: () => events.onRemove(parentPath, prop)
}); })
} }
// TODO: implement a hook to adjust the context menu // TODO: implement a hook to adjust the context menu
@ -431,7 +483,7 @@ export default class JSONNode extends Component {
click: () => events.onAppend(path, 'string') click: () => events.onAppend(path, 'string')
} }
] ]
}); })
// TODO: implement a hook to adjust the context menu // TODO: implement a hook to adjust the context menu
@ -456,7 +508,7 @@ export default class JSONNode extends Component {
return false return false
} }
static _rootName (data, options) { static _getRootName (data, options) {
return typeof options.name === 'string' return typeof options.name === 'string'
? options.name ? options.name
: (data.type === 'object' || data.type === 'array') : (data.type === 'object' || data.type === 'array')

View File

@ -3,6 +3,10 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Develop JSONEditor Next</title> <title>Develop JSONEditor Next</title>
<!-- For IE and Edge -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js"></script>
<script src="../dist/jsoneditor.js"></script> <script src="../dist/jsoneditor.js"></script>
<style> <style>
#container { #container {

View File

@ -134,6 +134,11 @@ ul.jsoneditor-list {
.jsoneditor-value { .jsoneditor-value {
border-radius: 1px; border-radius: 1px;
flex: 1 1 auto !important; flex: 1 1 auto !important;
p {
margin: 0;
padding: 0;
}
} }
.jsoneditor-property:focus, .jsoneditor-property:focus,