Floating menu starting to work (WIP)
This commit is contained in:
parent
a2f7f61389
commit
8425579718
|
@ -8,10 +8,25 @@ import FloatingMenu from './menu/FloatingMenu'
|
|||
import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
|
||||
import { getInnerText, insideRect, findParentWithAttribute } from '../utils/domUtils'
|
||||
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
||||
import { compileJSONPointer, SELECTED, SELECTED_END } from '../eson'
|
||||
import { compileJSONPointer, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
|
||||
|
||||
import type { ESONObjectProperty, ESON, SearchResultStatus, Path } from '../types'
|
||||
|
||||
// TODO: rename SELECTED, SELECTED_END, etc to AREA_*? It's used for both selection and hovering
|
||||
const SELECTED_CLASS_NAMES = {
|
||||
[SELECTED]: ' jsoneditor-selected',
|
||||
[SELECTED_END]: ' jsoneditor-selected jsoneditor-selected-end',
|
||||
[SELECTED_AFTER]: ' jsoneditor-selected jsoneditor-selected-after',
|
||||
[SELECTED_BEFORE]: ' jsoneditor-selected jsoneditor-selected-before',
|
||||
}
|
||||
|
||||
const HOVERED_CLASS_NAMES = {
|
||||
[SELECTED]: ' jsoneditor-hover',
|
||||
[SELECTED_END]: ' jsoneditor-hover jsoneditor-hover-end',
|
||||
[SELECTED_AFTER]: ' jsoneditor-hover jsoneditor-hover-after',
|
||||
[SELECTED_BEFORE]: ' jsoneditor-hover jsoneditor-hover-before',
|
||||
}
|
||||
|
||||
export default class JSONNode extends PureComponent {
|
||||
static URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url'
|
||||
|
||||
|
@ -21,6 +36,12 @@ export default class JSONNode extends PureComponent {
|
|||
hover: false
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (hoveredNode === this) {
|
||||
hoveredNode = null
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { props } = this
|
||||
|
||||
|
@ -38,9 +59,8 @@ export default class JSONNode extends PureComponent {
|
|||
renderJSONObject ({prop, index, data, options, events}) {
|
||||
const childCount = data.props.length
|
||||
const node = h('div', {
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
onKeyDown: this.handleKeyDown,
|
||||
key: 'node',
|
||||
onKeyDown: this.handleKeyDown,
|
||||
className: 'jsoneditor-node jsoneditor-object'
|
||||
}, [
|
||||
this.renderExpandButton(),
|
||||
|
@ -55,60 +75,49 @@ export default class JSONNode extends PureComponent {
|
|||
let childs
|
||||
if (data.expanded) {
|
||||
if (data.props.length > 0) {
|
||||
const props = data.props.map(prop => {
|
||||
return h('li', { key: prop.id, className: JSONNode.selectedClassName(prop.value.selected) },
|
||||
h(this.constructor, {
|
||||
path: this.props.path.concat(prop.name),
|
||||
prop,
|
||||
data: prop.value,
|
||||
options,
|
||||
events
|
||||
})
|
||||
)
|
||||
})
|
||||
const props = data.props.map(prop => h(this.constructor, {
|
||||
key: prop.id,
|
||||
path: this.props.path.concat(prop.name),
|
||||
prop,
|
||||
data: prop.value,
|
||||
options,
|
||||
events
|
||||
}))
|
||||
|
||||
childs = h('ul', {key: 'childs', className: 'jsoneditor-list'}, props)
|
||||
childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, props)
|
||||
}
|
||||
else {
|
||||
childs = h('ul', {key: 'childs', className: 'jsoneditor-list'},
|
||||
h('li', {},
|
||||
this.renderAppend('(empty object)')
|
||||
)
|
||||
childs = h('div', {key: 'childs', className: 'jsoneditor-list'},
|
||||
this.renderAppend('(empty object)')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const floatingMenu = this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
const floatingMenu = (data.selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
: null
|
||||
|
||||
return h('div', {
|
||||
className: 'jsoneditor-node-container ' + (this.state.hover ? ' jsoneditor-node-hover': ''),
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu, childs])
|
||||
}
|
||||
|
||||
static selectedClassName(selected: number) {
|
||||
return (selected === SELECTED)
|
||||
? ' jsoneditor-selected'
|
||||
: (selected === SELECTED_END)
|
||||
? 'jsoneditor-selected jsoneditor-selected-end'
|
||||
: ''
|
||||
}
|
||||
|
||||
// TODO: extract a function renderChilds shared by both renderJSONObject and renderJSONArray (rename .props and .items to .childs?)
|
||||
renderJSONArray ({prop, index, data, options, events}) {
|
||||
const childCount = data.items.length
|
||||
const node = h('div', {
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
onKeyDown: this.handleKeyDown,
|
||||
key: 'node',
|
||||
onKeyDown: this.handleKeyDown,
|
||||
className: 'jsoneditor-node jsoneditor-array'
|
||||
}, [
|
||||
this.renderExpandButton(),
|
||||
|
@ -123,39 +132,38 @@ export default class JSONNode extends PureComponent {
|
|||
let childs
|
||||
if (data.expanded) {
|
||||
if (data.items.length > 0) {
|
||||
const items = data.items.map((item, index) => {
|
||||
return h('li', { key : item.id, className: JSONNode.selectedClassName(prop.value.selected)},
|
||||
h(this.constructor, {
|
||||
path: this.props.path.concat(String(index)),
|
||||
index,
|
||||
data: item.value,
|
||||
options,
|
||||
events
|
||||
})
|
||||
)
|
||||
})
|
||||
childs = h('ul', {key: 'childs', className: 'jsoneditor-list'}, items)
|
||||
const items = data.items.map((item, index) => h(this.constructor, {
|
||||
key : item.id,
|
||||
path: this.props.path.concat(String(index)),
|
||||
index,
|
||||
data: item.value,
|
||||
options,
|
||||
events
|
||||
}))
|
||||
|
||||
childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, items)
|
||||
}
|
||||
else {
|
||||
childs = h('ul', {key: 'childs', className: 'jsoneditor-list'},
|
||||
h('li', {},
|
||||
this.renderAppend('(empty array)')
|
||||
)
|
||||
childs = h('div', {key: 'childs', className: 'jsoneditor-list'},
|
||||
this.renderAppend('(empty array)')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const floatingMenu = this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
const floatingMenu = (data.selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
: null
|
||||
|
||||
return h('div', {
|
||||
className: 'jsoneditor-node-container ' + (this.state.hover ? ' jsoneditor-node-hover': ''),
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu, childs])
|
||||
|
@ -163,8 +171,7 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
renderJSONValue ({prop, index, data, options}) {
|
||||
const node = h('div', {
|
||||
key: 'value',
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
key: 'node',
|
||||
onKeyDown: this.handleKeyDown,
|
||||
className: 'jsoneditor-node'
|
||||
}, [
|
||||
|
@ -178,20 +185,43 @@ export default class JSONNode extends PureComponent {
|
|||
this.renderError(data.error)
|
||||
])
|
||||
|
||||
const floatingMenu = this.renderFloatingMenu([
|
||||
// {text: 'String', onClick: this.props.events.onChangeType, type: 'checkbox', checked: false},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
const floatingMenu = (data.selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
// {text: 'String', onClick: this.props.events.onChangeType, type: 'checkbox', checked: false},
|
||||
{type: 'duplicate'},
|
||||
{type: 'cut'},
|
||||
{type: 'copy'},
|
||||
{type: 'paste'},
|
||||
{type: 'remove'}
|
||||
])
|
||||
: null
|
||||
|
||||
const insertArea = this.renderInsertArea()
|
||||
|
||||
return h('div', {
|
||||
className: 'jsoneditor-node-container ' + (this.state.hover ? ' jsoneditor-node-hover': ''),
|
||||
'data-path': compileJSONPointer(this.props.path),
|
||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu])
|
||||
}, [node, floatingMenu, insertArea])
|
||||
}
|
||||
|
||||
renderInsertArea () {
|
||||
const floatingMenu = (this.props.data.selected === SELECTED_AFTER)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'insertStructure'},
|
||||
{type: 'insertValue'},
|
||||
{type: 'insertObject'},
|
||||
{type: 'insertArray'},
|
||||
{type: 'paste'},
|
||||
])
|
||||
: null
|
||||
|
||||
return h('div', {
|
||||
key: 'menu',
|
||||
className: 'jsoneditor-insert-area',
|
||||
'data-area': 'after'
|
||||
}, [floatingMenu])
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -311,6 +341,12 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
getContainerClassName (selected, hover) {
|
||||
return 'jsoneditor-node-container' +
|
||||
(hover ? (HOVERED_CLASS_NAMES[hover]) : '') +
|
||||
(selected ? (SELECTED_CLASS_NAMES[selected]) : '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best position for the popover: right, above, below, or left
|
||||
* from the warning icon.
|
||||
|
@ -473,7 +509,7 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
renderFloatingMenu (items) {
|
||||
return h(FloatingMenu, {
|
||||
key: 'menu',
|
||||
key: 'floating-menu',
|
||||
path: this.props.path,
|
||||
events: this.props.events,
|
||||
items
|
||||
|
@ -495,23 +531,31 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
handleMouseOver = (event) => {
|
||||
event.stopPropagation()
|
||||
if (event.buttons === 0) { // no mouse button down, no dragging
|
||||
event.stopPropagation()
|
||||
|
||||
if (hoveredNode !== this) {
|
||||
const hover = (event.target.className.indexOf('jsoneditor-insert-area') !== -1)
|
||||
? SELECTED_AFTER
|
||||
: SELECTED
|
||||
|
||||
if (hoveredNode) {
|
||||
// FIXME: this may give issues when the hovered node doesn't exist anymore. check whether mounted
|
||||
hoveredNode.setState({hover: false})
|
||||
if (hoveredNode && hoveredNode !== this) {
|
||||
// FIXME: this gives issues when the hovered node doesn't exist anymore. check whether mounted?
|
||||
hoveredNode.setState({hover: null})
|
||||
}
|
||||
|
||||
this.setState({hover: true})
|
||||
hoveredNode = this
|
||||
if (hover !== this.state.hover) {
|
||||
this.setState({hover})
|
||||
hoveredNode = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave = (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({hover: false})
|
||||
// FIXME: this gives issues when the hovered node doesn't exist anymore. check whether mounted?
|
||||
hoveredNode.setState({hover: false})
|
||||
|
||||
this.setState({hover: null})
|
||||
}
|
||||
|
||||
handleOpenActionMenu = (event) => {
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
import { createFindKeyBinding } from '../utils/keyBindings'
|
||||
import { KEY_BINDINGS } from '../constants'
|
||||
|
||||
import type { ESON, ESONPatch, JSONPath, ESONSelection } from '../types'
|
||||
import type { ESON, ESONPatch, JSONPath, ESONSelection, ESONPointer } from '../types'
|
||||
|
||||
const AJV_OPTIONS = {
|
||||
allErrors: true,
|
||||
|
@ -101,6 +101,8 @@ export default class TreeMode extends Component {
|
|||
|
||||
onExpand: this.handleExpand,
|
||||
|
||||
onSelect: this.handleSelect,
|
||||
|
||||
// TODO: now we're passing not just events but also other methods. reorganize this or rename 'state.events'
|
||||
findKeyBinding: this.handleFindKeyBinding
|
||||
},
|
||||
|
@ -205,12 +207,14 @@ export default class TreeMode extends Component {
|
|||
h(Hammer, {
|
||||
id: this.id,
|
||||
direction: 'DIRECTION_VERTICAL',
|
||||
onTap: this.handleTap,
|
||||
onPanStart: this.handlePanStart,
|
||||
onPan: this.handlePan,
|
||||
onPanEnd: this.handlePanEnd
|
||||
},
|
||||
h('ul', {className: 'jsoneditor-list jsoneditor-root' + (data.selected ? ' jsoneditor-selected' : '')},
|
||||
h('div', {
|
||||
onMouseDown: this.handleTouchStart,
|
||||
onTouchStart: this.handleTouchStart,
|
||||
className: 'jsoneditor-list jsoneditor-root' +
|
||||
(data.selected ? ' jsoneditor-selected' : '')},
|
||||
h(Node, {
|
||||
data,
|
||||
events: state.events,
|
||||
|
@ -364,6 +368,7 @@ export default class TreeMode extends Component {
|
|||
moveUp(fromElement, 'property')
|
||||
}
|
||||
|
||||
this.setState({ selection : null })
|
||||
this.handlePatch(remove(path))
|
||||
}
|
||||
|
||||
|
@ -535,6 +540,11 @@ export default class TreeMode extends Component {
|
|||
this.handlePatch(sort(this.state.data, path, order))
|
||||
}
|
||||
|
||||
handleSelect = (selection: ESONSelection) => {
|
||||
console.log('handleSelect', selection)
|
||||
this.setState({ selection })
|
||||
}
|
||||
|
||||
handleExpand = (path, expanded, recurse) => {
|
||||
if (recurse) {
|
||||
const esonPath = toEsonPath(this.state.data, path)
|
||||
|
@ -661,22 +671,13 @@ export default class TreeMode extends Component {
|
|||
this.emitOnChange (actions, result.revert, result.data)
|
||||
}
|
||||
|
||||
handleTap = (event) => {
|
||||
const path = this.findDataPathFromElement(event.target.firstChild)
|
||||
if (this.state.selection) {
|
||||
this.setState({ selection: {start: {path}, end: {path}}})
|
||||
handleTouchStart = (event) => {
|
||||
const pointer = this.findESONPointerFromElement(event.target)
|
||||
if (pointer) {
|
||||
this.setState({ selection: {start: pointer, end: pointer}})
|
||||
}
|
||||
}
|
||||
|
||||
handlePanStart = (event) => {
|
||||
const path = this.findDataPathFromElement(event.target.firstChild)
|
||||
if (path) {
|
||||
this.setState({
|
||||
selection: {
|
||||
start: {path},
|
||||
end: {path}
|
||||
}
|
||||
})
|
||||
else {
|
||||
this.setState({ selection: null })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -713,6 +714,13 @@ export default class TreeMode extends Component {
|
|||
return attr ? parseJSONPointer(attr.replace(/\/-$/, '')) : null
|
||||
}
|
||||
|
||||
findESONPointerFromElement (element: Element) : ESONPointer {
|
||||
const path = this.findDataPathFromElement(element)
|
||||
const area = element && element.getAttribute && element.getAttribute('data-area') || null
|
||||
|
||||
return { path, area }
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the window vertically to the node with given path
|
||||
* @param {Path} path
|
||||
|
|
|
@ -84,11 +84,49 @@ const CREATE_TYPE = {
|
|||
onClick: () => events.onRemove(path),
|
||||
title: 'Remove'
|
||||
}, 'Remove'),
|
||||
|
||||
insertStructure: (path, events) => h('button', {
|
||||
key: 'insertStructure',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
title: 'Insert a new object with the same data structure as the item above'
|
||||
}, 'Insert structure'),
|
||||
|
||||
insertValue: (path, events) => h('button', {
|
||||
key: 'insertValue',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
title: 'Insert value'
|
||||
}, 'Insert value'),
|
||||
|
||||
insertObject: (path, events) => h('button', {
|
||||
key: 'insertObject',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
title: 'Insert Object'
|
||||
}, 'Insert Object'),
|
||||
|
||||
insertArray: (path, events) => h('button', {
|
||||
key: 'insertArray',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
title: 'Insert Array'
|
||||
}, 'Insert Array'),
|
||||
|
||||
}
|
||||
|
||||
export default class FloatingMenu extends PureComponent {
|
||||
componentDidMount () {
|
||||
setTimeout(() => {
|
||||
const firstButton = this.refs.root && this.refs.root.querySelector('button')
|
||||
if (firstButton) {
|
||||
firstButton.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return h('div', {className: MENU_CLASS_NAME}, this.props.items.map(item => {
|
||||
return h('div', {ref: 'root', className: MENU_CLASS_NAME}, this.props.items.map(item => {
|
||||
const type = typeof item === 'string' ? item : item.type
|
||||
const createType = CREATE_TYPE[type]
|
||||
if (createType) {
|
||||
|
|
|
@ -10,7 +10,7 @@ let lastInputName = null
|
|||
// TODO: create a constants file with the CSS names that are used in domSelector
|
||||
const SEARCH_TEXT_CLASS_NAME = 'jsoneditor-search-text'
|
||||
const SEARCH_COMPONENT_CLASS_NAME = 'jsoneditor-search'
|
||||
const NODE_CONTAINER_CLASS_NAME = 'jsoneditor-node'
|
||||
const NODE_CONTAINER_CLASS_NAME = 'jsoneditor-node-container'
|
||||
const CONTENTS_CONTAINER_CLASS_NAME = 'jsoneditor-tree-contents'
|
||||
const PROPERTY_CLASS_NAME = 'jsoneditor-property'
|
||||
const VALUE_CLASS_NAME = 'jsoneditor-value'
|
||||
|
|
14
src/eson.js
14
src/eson.js
|
@ -22,6 +22,8 @@ type RecurseCallback = (value: ESON, path: Path, root: ESON) => ESON
|
|||
|
||||
export const SELECTED = 1
|
||||
export const SELECTED_END = 2
|
||||
export const SELECTED_BEFORE = 3
|
||||
export const SELECTED_AFTER = 4
|
||||
|
||||
/**
|
||||
* Expand function which will expand all nodes
|
||||
|
@ -263,7 +265,7 @@ export function search (eson: ESON, text: string): ESONPointer[] {
|
|||
const parentPath = initial(path)
|
||||
const parent = getIn(eson, toEsonPath(eson, parentPath))
|
||||
if (parent.type === 'Object') {
|
||||
results.push({path, field: 'property'})
|
||||
results.push({path, area: 'property'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +273,7 @@ export function search (eson: ESON, text: string): ESONPointer[] {
|
|||
// check value
|
||||
if (value.type === 'value') {
|
||||
if (containsCaseInsensitive(value.value, text)) {
|
||||
results.push({path, field: 'value'})
|
||||
results.push({path, area: 'value'})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -335,13 +337,13 @@ export function applySearchResults (eson: ESON, searchResults: ESONPointer[], ac
|
|||
let updatedEson = eson
|
||||
|
||||
searchResults.forEach(function (searchResult) {
|
||||
if (searchResult.field === 'value') {
|
||||
if (searchResult.area === 'value') {
|
||||
const esonPath = toEsonPath(updatedEson, searchResult.path).concat('searchResult')
|
||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||
updatedEson = setIn(updatedEson, esonPath, value)
|
||||
}
|
||||
|
||||
if (searchResult.field === 'property') {
|
||||
if (searchResult.area === 'property') {
|
||||
const esonPath = toEsonPath(updatedEson, searchResult.path)
|
||||
const propertyPath = initial(esonPath).concat('searchResult')
|
||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||
|
@ -366,8 +368,10 @@ export function applySelection (eson: ESON, selection: ESONSelection) {
|
|||
|
||||
if (rootPath.length === selection.start.path.length || rootPath.length === selection.end.path.length) {
|
||||
// select a single node
|
||||
return setIn(eson, rootEsonPath.concat(['selected']), SELECTED_END)
|
||||
const selectionType = (selection.start.area === 'after') ? SELECTED_AFTER : SELECTED_END
|
||||
console.log('selectionType', selectionType, selection)
|
||||
// FIXME: actually mark the end index as SELECTED_END, currently we select the first index
|
||||
return setIn(eson, rootEsonPath.concat(['selected']), selectionType)
|
||||
}
|
||||
else {
|
||||
// select multiple childs of an object or array
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
@floating-menu-color: #fff;
|
||||
// @selectedColor: #e5e5e5;
|
||||
@selectedColor: #ffed99;
|
||||
@hoverColor: rgba(10, 10, 10, 0.05);
|
||||
@hoverColor: #f2f2f2;
|
||||
@hoverAndSelectedColor: #F2E191;
|
||||
|
||||
.jsoneditor {
|
||||
border: 1px solid @theme-color;
|
||||
|
@ -119,16 +120,17 @@
|
|||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
ul.jsoneditor-list {
|
||||
div.jsoneditor-list {
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
margin: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* no left padding for the root ul element */
|
||||
.jsoneditor-contents > ul.jsoneditor-list {
|
||||
/* no left padding for the root div element */
|
||||
.jsoneditor-contents > div.jsoneditor-list {
|
||||
padding-left: 2px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.jsoneditor-property,
|
||||
|
@ -261,10 +263,6 @@ div.jsoneditor-value.jsoneditor-empty::after {
|
|||
content: 'value';
|
||||
}
|
||||
|
||||
.jsoneditor-selected {
|
||||
background-color: @selectedColor;
|
||||
}
|
||||
|
||||
.jsoneditor-highlight {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
@ -567,8 +565,90 @@ div.jsoneditor-node-container {
|
|||
position: relative;
|
||||
transition: background-color 100ms ease-in;
|
||||
|
||||
// TODO: can the hover/select css be simplified?
|
||||
|
||||
&.jsoneditor-selected {
|
||||
background-color: @selectedColor;
|
||||
|
||||
&.jsoneditor-hover {
|
||||
background-color: @hoverAndSelectedColor;
|
||||
|
||||
&.jsoneditor-hover-after {
|
||||
background-color: @selectedColor;
|
||||
|
||||
div.jsoneditor-insert-area {
|
||||
border: 1px dashed gray;
|
||||
background-color: @hoverColor;
|
||||
}
|
||||
|
||||
&.jsoneditor-selected-after {
|
||||
div.jsoneditor-insert-area {
|
||||
border: 1px dashed #f4af41;
|
||||
background-color: @hoverAndSelectedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.jsoneditor-selected-after {
|
||||
background-color: inherit;
|
||||
|
||||
&.jsoneditor-hover {
|
||||
background-color: @hoverColor;
|
||||
|
||||
&.jsoneditor-hover-after {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
div.jsoneditor-insert-area {
|
||||
border: 1px dashed #f4af41;
|
||||
background: @selectedColor;
|
||||
}
|
||||
}
|
||||
|
||||
// hovering nested elements
|
||||
.jsoneditor-hover {
|
||||
background-color: @hoverAndSelectedColor;
|
||||
|
||||
&.jsoneditor-hover-after {
|
||||
background-color: inherit;
|
||||
|
||||
div.jsoneditor-insert-area {
|
||||
border: 1px dashed #f4af41;
|
||||
background: @hoverAndSelectedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.jsoneditor-hover {
|
||||
background-color: @hoverColor;
|
||||
|
||||
&.jsoneditor-hover-after {
|
||||
background-color: inherit;
|
||||
|
||||
div.jsoneditor-insert-area {
|
||||
border: 1px dashed gray;
|
||||
background-color: @hoverColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.jsoneditor-insert-area {
|
||||
@height: 8px;
|
||||
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: @height;
|
||||
left: 0;
|
||||
bottom: -@height/2;
|
||||
border: 1px transparent;
|
||||
box-sizing: border-box;
|
||||
z-index: 1; // must be on top of next node, it overlaps a bit
|
||||
}
|
||||
|
||||
div.jsoneditor-floating-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
|
@ -599,7 +679,9 @@ div.jsoneditor-node-container {
|
|||
border-right: 1px solid lighten(@floating-menu-background, 10%);
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: lighten(@floating-menu-background, 10%);
|
||||
}
|
||||
|
@ -616,14 +698,6 @@ div.jsoneditor-node-container {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.jsoneditor-node-hover {
|
||||
background-color: @hoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
.jsoneditor-selected-end > .jsoneditor-node-container > div.jsoneditor-floating-menu {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
/******************************* **********************************/
|
||||
|
|
|
@ -33,7 +33,7 @@ export type JSONArrayType = JSONType[]
|
|||
/********************** TYPES FOR THE ESON OBJECT MODEL *************************/
|
||||
|
||||
export type SearchResultStatus = 'normal' | 'active'
|
||||
export type ESONPointerField = 'value' | 'property'
|
||||
export type ESONPointerArea = 'value' | 'property' | 'before' | 'after'
|
||||
|
||||
export type ESONObjectProperty = {
|
||||
id: number,
|
||||
|
@ -78,7 +78,7 @@ export type ESONPath = string[]
|
|||
|
||||
export type ESONPointer = {
|
||||
path: JSONPath, // TODO: change path to an ESONPath?
|
||||
field?: ESONPointerField
|
||||
area?: ESONPointerArea
|
||||
}
|
||||
|
||||
export type ESONSelection = {
|
||||
|
|
|
@ -194,12 +194,12 @@ test('search', t => {
|
|||
// printJSON(searchResults)
|
||||
|
||||
t.deepEqual(searchResults, [
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'},
|
||||
{path: ['str'], field: 'value'},
|
||||
{path: ['nill'], field: 'property'},
|
||||
{path: ['nill'], field: 'value'},
|
||||
{path: ['bool'], field: 'property'},
|
||||
{path: ['bool'], field: 'value'}
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
|
||||
{path: ['str'], area: 'value'},
|
||||
{path: ['nill'], area: 'property'},
|
||||
{path: ['nill'], area: 'value'},
|
||||
{path: ['bool'], area: 'property'},
|
||||
{path: ['bool'], area: 'value'}
|
||||
])
|
||||
|
||||
const activeSearchResult = searchResults[0]
|
||||
|
@ -219,30 +219,30 @@ test('search', t => {
|
|||
|
||||
test('nextSearchResult', t => {
|
||||
const searchResults = [
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'},
|
||||
{path: ['str'], field: 'value'},
|
||||
{path: ['nill'], field: 'property'},
|
||||
{path: ['nill'], field: 'value'},
|
||||
{path: ['bool'], field: 'property'},
|
||||
{path: ['bool'], field: 'value'}
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
|
||||
{path: ['str'], area: 'value'},
|
||||
{path: ['nill'], area: 'property'},
|
||||
{path: ['nill'], area: 'value'},
|
||||
{path: ['bool'], area: 'property'},
|
||||
{path: ['bool'], area: 'value'}
|
||||
]
|
||||
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['nill'], field: 'property'}),
|
||||
{path: ['nill'], field: 'value'})
|
||||
{path: ['nill'], area: 'property'}),
|
||||
{path: ['nill'], area: 'value'})
|
||||
|
||||
// wrap around
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['bool'], field: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'})
|
||||
{path: ['bool'], area: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'})
|
||||
|
||||
// return first when current is not found
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['non', 'existing'], field: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'})
|
||||
{path: ['non', 'existing'], area: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'})
|
||||
|
||||
// return null when searchResults are empty
|
||||
t.deepEqual(nextSearchResult([], {path: ['non', 'existing'], field: 'value'}), null)
|
||||
t.deepEqual(nextSearchResult([], {path: ['non', 'existing'], area: 'value'}), null)
|
||||
})
|
||||
|
||||
test('previousSearchResult', t => {
|
||||
|
|
Loading…
Reference in New Issue