diff --git a/src/jsoneditor/components/JSONNode.js b/src/jsoneditor/components/JSONNode.js index cd2d2e5..61a65c5 100644 --- a/src/jsoneditor/components/JSONNode.js +++ b/src/jsoneditor/components/JSONNode.js @@ -9,7 +9,8 @@ import { stringConvert, valueType, isUrl } from '../utils/typeUtils' import { compileJSONPointer, META, - SELECTED, SELECTED_START, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE, SELECTED_FIRST, SELECTED_LAST + SELECTED, SELECTED_START, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE, + SELECTED_FIRST, SELECTED_LAST } from '../eson' const MENU_ITEMS_OBJECT = [ @@ -108,9 +109,12 @@ export default class JSONNode extends PureComponent { ? [ this.renderTag(`${props.length} ${props.length === 1 ? 'prop' : 'props'}`, `Object containing ${props.length} ${props.length === 1 ? 'property' : 'properties'}`), - this.renderDelimiter('}', 'jsoneditor-delimiter-start'), + this.renderDelimiter('}', 'jsoneditor-delimiter-end jsoneditor-delimiter-collapsed'), + this.renderInsertAfter() ] - : null, + : [ + this.renderInsertBefore() + ], this.renderError(meta.error) ]) @@ -129,24 +133,27 @@ export default class JSONNode extends PureComponent { childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, propsChilds) } else { - childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, + childs = h('div', {key: 'childs', className: 'jsoneditor-list', 'data-area': 'emptyBefore'}, this.renderAppend('(empty object)') ) } } const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_OBJECT, meta.selected) - const insertArea = this.renderInsertBeforeArea() const nodeEnd = meta.expanded - ? this.renderDelimiter('}', 'jsoneditor-delimiter-end') + ? h('div', {key: 'node-end', className: 'jsoneditor-node-end', 'data-area': 'empty'}, [ + this.renderDelimiter('}', 'jsoneditor-delimiter-end'), + this.renderInsertAfter() + ]) : null return h('div', { 'data-path': compileJSONPointer(meta.path), + 'data-area': 'empty', className: this.getContainerClassName(meta.selected, this.state.hover), // onMouseOver: this.handleMouseOver, // onMouseLeave: this.handleMouseLeave - }, [nodeStart, floatingMenu, insertArea, childs, nodeEnd]) + }, [floatingMenu, nodeStart, childs, nodeEnd]) } renderJSONArray () { @@ -158,7 +165,6 @@ export default class JSONNode extends PureComponent { className: 'jsoneditor-node jsoneditor-array' }, [ this.renderExpandButton(), - // this.renderDelimiter('\u2611'), this.renderProperty(), this.renderSeparator(), this.renderDelimiter('[', 'jsoneditor-delimiter-start'), @@ -166,9 +172,12 @@ export default class JSONNode extends PureComponent { ? [ this.renderTag(`${count} ${count === 1 ? 'item' : 'items'}`, `Array containing ${count} item${count === 1 ? 'item' : 'items'}`), - this.renderDelimiter(']', 'jsoneditor-delimiter-start'), + this.renderDelimiter(']', 'jsoneditor-delimiter-end jsoneditor-delimiter-collapsed'), + this.renderInsertAfter(), ] - : null, + : [ + this.renderInsertBefore() + ], this.renderError(meta.error) ]) @@ -186,24 +195,27 @@ export default class JSONNode extends PureComponent { childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, items) } else { - childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, + childs = h('div', {key: 'childs', className: 'jsoneditor-list', 'data-area': 'emptyBefore'}, this.renderAppend('(empty array)') ) } } const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_ARRAY, meta.selected) - const insertArea = this.renderInsertBeforeArea() const nodeEnd = meta.expanded - ? this.renderDelimiter(']', 'jsoneditor-delimiter-end') + ? h('div', {key: 'node-end', className: 'jsoneditor-node-end', 'data-area': 'empty'}, [ + this.renderDelimiter(']', 'jsoneditor-delimiter-end'), + this.renderInsertAfter() + ]) : null return h('div', { 'data-path': compileJSONPointer(meta.path), + 'data-area': 'empty', className: this.getContainerClassName(meta.selected, this.state.hover), // onMouseOver: this.handleMouseOver, // onMouseLeave: this.handleMouseLeave - }, [nodeStart, floatingMenu, insertArea, childs, nodeEnd]) + }, [floatingMenu, nodeStart, childs, nodeEnd]) } renderJSONValue () { @@ -214,38 +226,42 @@ export default class JSONNode extends PureComponent { className: 'jsoneditor-node' }, [ this.renderPlaceholder(), - // this.renderDelimiter('\u2611'), this.renderProperty(), this.renderSeparator(), this.renderValue(meta.value, meta.searchValue, this.props.options), - // this.renderDelimiter('\u21B2'), - // this.renderDelimiter('\u23CE'), + this.renderInsertAfter(), this.renderError(meta.error) ]) const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_VALUE, meta.selected) - const insertArea = this.renderInsertBeforeArea() + // const insertArea = this.renderInsertBeforeArea() return h('div', { 'data-path': compileJSONPointer(meta.path), + 'data-area': 'empty', className: this.getContainerClassName(meta.selected, this.state.hover), // onMouseOver: this.handleMouseOver, // onMouseLeave: this.handleMouseLeave - }, [node, floatingMenu, insertArea]) + }, [node, floatingMenu]) } - renderInsertBeforeArea () { - const floatingMenu = ((this.props.value[META].selected & SELECTED_BEFORE) !== 0) - ? this.renderFloatingMenu(MENU_ITEMS_INSERT_BEFORE, - SELECTED + SELECTED_END + SELECTED_FIRST) - : null - + renderInsertBefore () { return h('div', { - key: 'menu', - className: 'jsoneditor-insert-area', + key: 'insert', + className: 'jsoneditor-insert jsoneditor-insert-before', + title: 'Insert a new item or paste clipboard', 'data-area': 'before' - }, [floatingMenu]) + }) + } + + renderInsertAfter () { + return h('div', { + key: 'insert', + className: 'jsoneditor-insert jsoneditor-insert-after', + title: 'Insert a new item or paste clipboard after this line', + 'data-area': 'after' + }) } /** @@ -256,20 +272,30 @@ export default class JSONNode extends PureComponent { renderAppend (text) { return h('div', { 'data-path': compileJSONPointer(this.props.value[META].path) + '/-', + 'data-area': 'empty', className: 'jsoneditor-node', onKeyDown: this.handleKeyDownAppend }, [ - this.renderPlaceholder(), + this.renderPlaceholder('before'), this.renderReadonly(text) ]) } - renderPlaceholder () { - return h('div', {key: 'placeholder', className: 'jsoneditor-button-placeholder'}) + renderPlaceholder (dataArea = 'value') { + return h('div', { + key: 'placeholder', + 'data-area': dataArea, + className: 'jsoneditor-button-placeholder' + }) } - renderReadonly (text, title = null) { - return h('div', {key: 'readonly', className: 'jsoneditor-readonly', title}, text) + renderReadonly (text, title = null, dataArea = 'before') { + return h('div', { + key: 'readonly', + 'data-area': dataArea, + className: 'jsoneditor-readonly', + title + }, text) } renderTag (text, title = null) { @@ -302,7 +328,6 @@ export default class JSONNode extends PureComponent { if (editable) { return [ - // this.renderDelimiter('"'), h('div', { key: 'property', className: 'jsoneditor-property' + emptyClassName + searchClassName, @@ -311,7 +336,6 @@ export default class JSONNode extends PureComponent { spellCheck: 'false', onBlur: this.handleChangeProperty }, escapedPropName), - // this.renderDelimiter('" '), ] } else { @@ -329,11 +353,19 @@ export default class JSONNode extends PureComponent { return null } - return h('div', {key: 'separator', className: 'jsoneditor-delimiter'}, ':') + return h('div', { + key: 'separator', + className: 'jsoneditor-delimiter', + 'data-area': 'value' + }, ':') } renderDelimiter (text, className = '') { - return h('div', {key: text, className: 'jsoneditor-delimiter ' + className}, text) + return h('div', { + key: text, + 'data-area': 'value', + className: 'jsoneditor-delimiter ' + className + }, text) } renderValue (value, searchResult, options) { @@ -387,19 +419,31 @@ export default class JSONNode extends PureComponent { getContainerClassName (selected, hover) { let classNames = ['jsoneditor-node-container'] - if ((selected & SELECTED) !== 0) { classNames.push('jsoneditor-selected') } - if ((selected & SELECTED_START) !== 0) { classNames.push('jsoneditor-selected-start') } - if ((selected & SELECTED_END) !== 0) { classNames.push('jsoneditor-selected-end') } - if ((selected & SELECTED_FIRST) !== 0) { classNames.push('jsoneditor-selected-first') } - if ((selected & SELECTED_LAST) !== 0) { classNames.push('jsoneditor-selected-last') } - if ((selected & SELECTED_BEFORE) !== 0) { classNames.push('jsoneditor-selected-insert-area-before') } - if ((selected & SELECTED_AFTER) !== 0) { classNames.push('jsoneditor-selected-insert-area-after') } + if ((selected & SELECTED_BEFORE) !== 0) { + classNames.push('jsoneditor-selected-insert-before') + } + else if ((selected & SELECTED_AFTER) !== 0) { + classNames.push('jsoneditor-selected-insert-after') + } + else { + if ((selected & SELECTED) !== 0) { classNames.push('jsoneditor-selected') } + if ((selected & SELECTED_START) !== 0) { classNames.push('jsoneditor-selected-start') } + if ((selected & SELECTED_END) !== 0) { classNames.push('jsoneditor-selected-end') } + if ((selected & SELECTED_FIRST) !== 0) { classNames.push('jsoneditor-selected-first') } + if ((selected & SELECTED_LAST) !== 0) { classNames.push('jsoneditor-selected-last') } + } - if ((hover & SELECTED) !== 0) { classNames.push('jsoneditor-hover') } - if ((hover & SELECTED_START) !== 0) { classNames.push('jsoneditor-hover-start') } - if ((hover & SELECTED_END) !== 0) { classNames.push('jsoneditor-hover-end') } - if ((hover & SELECTED_BEFORE) !== 0) { classNames.push('jsoneditor-hover-insert-area-before') } - if ((hover & SELECTED_AFTER) !== 0) { classNames.push('jsoneditor-hover-insert-area-after') } + if ((hover & SELECTED_BEFORE) !== 0) { + classNames.push('jsoneditor-hover-insert-before') + } + else if ((hover & SELECTED_AFTER) !== 0) { + classNames.push('jsoneditor-hover-insert-after') + } + else { + if ((hover & SELECTED) !== 0) { classNames.push('jsoneditor-hover') } + if ((hover & SELECTED_START) !== 0) { classNames.push('jsoneditor-hover-start') } + if ((hover & SELECTED_END) !== 0) { classNames.push('jsoneditor-hover-end') } + } return classNames.join(' ') } diff --git a/src/jsoneditor/components/TreeMode.js b/src/jsoneditor/components/TreeMode.js index c4fdc4f..86e8215 100644 --- a/src/jsoneditor/components/TreeMode.js +++ b/src/jsoneditor/components/TreeMode.js @@ -695,6 +695,9 @@ export default class TreeMode extends PureComponent { const clickedOnEmptySpace = (event.target.nodeName === 'DIV') && (event.target.contentEditable !== 'true') + // TODO: cleanup + // console.log('handleTouchStart', clickedOnEmptySpace && pointer, pointer && this.selectionFromESONPointer(pointer)) + if (clickedOnEmptySpace && pointer) { this.setState({ selection: this.selectionFromESONPointer(pointer)}) } @@ -707,9 +710,17 @@ export default class TreeMode extends PureComponent { const selection = this.state.selection const path = this.findDataPathFromElement(event.target.firstChild) if (path && selection && !isEqual(path, selection.end)) { + + // TODO: cleanup + // console.log('handlePan', { + // start: selection.start || selection.before || selection.after || selection.empty || selection.emptyBefore, + // end: path + // }) + + // FIXME: when selection.empty, start should be set to the next node this.setState({ selection: { - start: selection.start || selection.before || selection.after, + start: selection.start || selection.before || selection.after || selection.empty || selection.emptyBefore, end: path } }) @@ -767,6 +778,12 @@ export default class TreeMode extends PureComponent { else if (pointer.area === 'before') { return {before: pointer.path} } + else if (pointer.area === 'empty') { + return {empty: pointer.path} + } + else if (pointer.area === 'emptyBefore') { + return {emptyBefore: pointer.path} + } else { return {start: pointer.path, end: pointer.path} } diff --git a/src/jsoneditor/components/img/jsoneditor-icons.svg b/src/jsoneditor/components/img/jsoneditor-icons.svg index 1b40068..81e172b 100644 --- a/src/jsoneditor/components/img/jsoneditor-icons.svg +++ b/src/jsoneditor/components/img/jsoneditor-icons.svg @@ -11,7 +11,7 @@ height="144" id="svg4136" version="1.1" - inkscape:version="0.91 r" + inkscape:version="0.91 r13725" sodipodi:docname="jsoneditor-icons.svg"> JSON Editor Icons @@ -39,16 +39,16 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" - inkscape:window-height="1028" + inkscape:window-height="1027" id="namedview4144" showgrid="true" - inkscape:zoom="4" - inkscape:cx="97.217248" - inkscape:cy="59.950227" + inkscape:zoom="5.6568542" + inkscape:cx="113.02949" + inkscape:cy="98.794192" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg4136" + inkscape:current-layer="g4394" showguides="false" borderlayer="false" inkscape:showpageshadow="true" @@ -67,7 +67,7 @@ width="16" height="16" id="svg_1" - style="fill:#1aae1c;fill-opacity:1;stroke:none;stroke-width:0" /> + style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" /> + + + + + diff --git a/src/jsoneditor/components/jsoneditor.css b/src/jsoneditor/components/jsoneditor.css index 1eda7a3..866cef0 100644 --- a/src/jsoneditor/components/jsoneditor.css +++ b/src/jsoneditor/components/jsoneditor.css @@ -168,6 +168,15 @@ display: inline-flex; flex-direction: row; } +.jsoneditor-node-end { + display: flex; + height: 20px; } + .jsoneditor-node-end .jsoneditor-button-container:hover { + visibility: visible; } + +.jsoneditor-root > .jsoneditor-node-container > .jsoneditor-node-end > .jsoneditor-button-container { + display: none; } + .jsoneditor-node > div { flex: 0 0 auto; } @@ -202,7 +211,8 @@ div.jsoneditor-list { min-width: auto; } .jsoneditor-button-container { - font-size: 0; } + font-size: 0; + display: inline-block; } .jsoneditor-property, .jsoneditor-value { @@ -240,6 +250,8 @@ div.jsoneditor-list { .jsoneditor-delimiter-end { padding-left: 5px; border-left: 20px solid transparent; } + .jsoneditor-delimiter-end.jsoneditor-delimiter-collapsed { + border-left: none; } .jsoneditor-readonly:focus, .jsoneditor-readonly:hover { @@ -355,193 +367,13 @@ button.jsoneditor-button.jsoneditor-actionmenu:focus, button.jsoneditor-button.jsoneditor-actionmenu.jsoneditor-visible { background-position: -50px -50px; } -/******************************* Action Menu **********************************/ -div.jsoneditor-actionmenu { - position: absolute; - box-sizing: border-box; - z-index: 99999; - top: 20px; - left: 18px; - /* 20px - 2px where 2px half the difference between 24x24 icons of the menu and the 20x20 icons of the editor */ - background: white; - border: 1px solid #d3d3d3; - box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); } - -div.jsoneditor-actionmenu.jsoneditor-actionmenu-top { - top: auto; - bottom: 20px; } - -div.jsoneditor-modemenu.jsoneditor-modemenu { - top: 26px; - left: 0; } - -div.jsoneditor-menu-item { - line-height: 0; - font-size: 0; } - -button.jsoneditor-menu-button { - width: 136px; - height: 24px; - padding: 0; - margin: 0; - line-height: 24px; - background: transparent; - border: transparent; - display: inline-block; - box-sizing: border-box; - cursor: pointer; - color: #4d4d4d; - font-size: 10pt; - font-family: arial, sans-serif; - text-align: left; } - -button.jsoneditor-menu-button:hover, -button.jsoneditor-menu-button:focus { - color: #1A1A1A; - background-color: #f5f5f5; - outline: none; } - -button.jsoneditor-menu-button.jsoneditor-selected { - color: white; - background-color: #ee422e; } - -button.jsoneditor-menu-default { - width: 104px; - /* 136px - 32px */ } - -button.jsoneditor-menu-expand { - width: 32px; - float: right; - border-left: 1px solid #e5e5e5; } - -span.jsoneditor-icon { - float: left; - width: 24px; - height: 24px; - border: none; - padding: 0; - margin: 0; - background-image: url("img/jsoneditor-icons.svg"); } - -span.jsoneditor-icon.jsoneditor-icon-expand { - float: right; - width: 24px; - margin: 0 4px; - background-position: 0 -72px !important; - opacity: 0.4; } - -div.jsoneditor-menu-item button.jsoneditor-menu-button:hover span.jsoneditor-icon-expand, -div.jsoneditor-menu-item button:focus span.jsoneditor-icon-expand { - opacity: 1; } - -span.jsoneditor-text { - display: inline-block; - line-height: 24px; } - -div.jsoneditor-menu-separator { - height: 0; - border-top: 1px solid #e5e5e5; - padding-top: 5px; - margin-top: 5px; } - +/*********************************** Menu *************************************/ div.jsoneditor-menu-panel-right { float: right; max-width: 100%; } -button.jsoneditor-remove span.jsoneditor-icon { - background-position: -24px -24px; } - -button.jsoneditor-remove:hover span.jsoneditor-icon, -button.jsoneditor-remove:focus span.jsoneditor-icon { - background-position: -24px 0; } - -button.jsoneditor-insert span.jsoneditor-icon { - background-position: 0 -24px; } - -button.jsoneditor-insert:hover span.jsoneditor-icon, -button.jsoneditor-insert:focus span.jsoneditor-icon { - background-position: 0 0; } - -button.jsoneditor-duplicate span.jsoneditor-icon { - background-position: -48px -24px; } - -button.jsoneditor-duplicate:hover span.jsoneditor-icon, -button.jsoneditor-duplicate:focus span.jsoneditor-icon { - background-position: -48px 0; } - -button.jsoneditor-sort-asc span.jsoneditor-icon { - background-position: -168px -24px; } - -button.jsoneditor-sort-asc:hover span.jsoneditor-icon, -button.jsoneditor-sort-asc:focus span.jsoneditor-icon { - background-position: -168px 0; } - -button.jsoneditor-sort-desc span.jsoneditor-icon { - background-position: -192px -24px; } - -button.jsoneditor-sort-desc:hover span.jsoneditor-icon, -button.jsoneditor-sort-desc:focus span.jsoneditor-icon { - background-position: -192px 0; } - -div.jsoneditor-submenu { - visibility: hidden; - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease-out; - box-shadow: inset 0 10px 10px -10px rgba(128, 128, 128, 0.5), inset 0 -10px 10px -10px rgba(128, 128, 128, 0.5); } - -div.jsoneditor-submenu.jsoneditor-expanded { - visibility: visible; - max-height: 104px; - /* 4 * 24px + 2 * 5px */ - /* FIXME: shouldn't rely on max-height equal to 4 items, should be flexible */ } - -div.jsoneditor-submenu.jsoneditor-collapsing { - visibility: visible; - max-height: 0; } - -div.jsoneditor-submenu button { - padding-left: 24px; } - -div.jsoneditor-submenu div.jsoneditor-menu-item:first-child { - margin-top: 5px; } - -div.jsoneditor-submenu div.jsoneditor-menu-item:last-child { - margin-bottom: 5px; } - -button.jsoneditor-type-string span.jsoneditor-icon { - background-position: -144px -24px; } - -button.jsoneditor-type-string:hover span.jsoneditor-icon, -button.jsoneditor-type-string:focus span.jsoneditor-icon, -button.jsoneditor-type-string.jsoneditor-selected span.jsoneditor-icon { - background-position: -144px 0; } - -button.jsoneditor-type-value span.jsoneditor-icon { - background-position: -120px -24px; } - -button.jsoneditor-type-value:hover span.jsoneditor-icon, -button.jsoneditor-type-value:focus span.jsoneditor-icon, -button.jsoneditor-type-value.jsoneditor-selected span.jsoneditor-icon { - background-position: -120px 0; } - -button.jsoneditor-type-Object span.jsoneditor-icon { - background-position: -72px -24px; } - -button.jsoneditor-type-Object:hover span.jsoneditor-icon, -button.jsoneditor-type-Object:focus span.jsoneditor-icon, -button.jsoneditor-type-Object.jsoneditor-selected span.jsoneditor-icon { - background-position: -72px 0; } - -button.jsoneditor-type-Array span.jsoneditor-icon { - background-position: -96px -24px; } - -button.jsoneditor-type-Array:hover span.jsoneditor-icon, -button.jsoneditor-type-Array:focus span.jsoneditor-icon, -button.jsoneditor-type-Array.jsoneditor-selected span.jsoneditor-icon { - background-position: -96px 0; } - -/******************************* Floatting Menu **********************************/ +/******************************* Action Menu **********************************/ +/******************************* Floating Menu **********************************/ div.jsoneditor-node-container { position: relative; transition: background-color 100ms ease-in; } @@ -568,6 +400,22 @@ div.jsoneditor-node-container { border-left-color: #ffed99; } div.jsoneditor-node-container.jsoneditor-selected .jsoneditor-list { border-left-color: #ffed99; } + div.jsoneditor-node-container div.jsoneditor-insert { + width: 20px; + height: 20px; } + div.jsoneditor-node-container div.jsoneditor-insert:hover { + background: url("img/jsoneditor-icons.svg") -2px -26px; } + div.jsoneditor-node-container.jsoneditor-selected-insert-after { + border-bottom: 1px dashed #c0c0c0; + margin-bottom: -1px; } + div.jsoneditor-node-container.jsoneditor-selected-insert-after > .jsoneditor-node > .jsoneditor-insert-after, + div.jsoneditor-node-container.jsoneditor-selected-insert-after > .jsoneditor-node-end > .jsoneditor-insert-after { + background-image: url("img/jsoneditor-icons.svg"); + background-position: -2px -2px !important; } + div.jsoneditor-node-container.jsoneditor-selected-insert-before > .jsoneditor-node > .jsoneditor-insert-before, + div.jsoneditor-node-container.jsoneditor-selected-insert-before > .jsoneditor-node-end > .jsoneditor-insert-before { + background-image: url("img/jsoneditor-icons.svg"); + background-position: -2px -2px !important; } div.jsoneditor-node-container.jsoneditor-hover > .jsoneditor-node > .jsoneditor-button-container, div.jsoneditor-node-container.jsoneditor-hover > .jsoneditor-node > .jsoneditor-button-placeholder { background-color: #d3d3d3; } diff --git a/src/jsoneditor/components/jsoneditor.scss b/src/jsoneditor/components/jsoneditor.scss index 5e17f6e..5aeadd9 100644 --- a/src/jsoneditor/components/jsoneditor.scss +++ b/src/jsoneditor/components/jsoneditor.scss @@ -123,6 +123,23 @@ $insert-area-height: 6px; flex-direction: row; } +.jsoneditor-node-end { + display: flex; + height: $line-height; + + .jsoneditor-button-container { + //visibility: hidden; // FIXME + + &:hover { + visibility: visible; + } + } +} + +.jsoneditor-root > .jsoneditor-node-container > .jsoneditor-node-end > .jsoneditor-button-container { + display: none; +} + .jsoneditor-node > div { flex: 0 0 auto; } @@ -167,6 +184,7 @@ div.jsoneditor-list { .jsoneditor-button-container { font-size: 0; + display: inline-block; } .jsoneditor-property, @@ -218,6 +236,10 @@ div.jsoneditor-list { .jsoneditor-delimiter-end { padding-left: $input-padding; border-left: $line-height solid transparent; + + &.jsoneditor-delimiter-collapsed { + border-left: none; + } } .jsoneditor-readonly:focus, @@ -362,241 +384,239 @@ button.jsoneditor-button.jsoneditor-actionmenu.jsoneditor-visible { background-position: -50px -50px; } - - -/******************************* Action Menu **********************************/ - -// TODO: move into a separate file like menu/Menu.less - -div.jsoneditor-actionmenu { - position: absolute; - box-sizing: border-box; - z-index: 99999; - top: 20px; - left: 18px; /* 20px - 2px where 2px half the difference between 24x24 icons of the menu and the 20x20 icons of the editor */ - background: white; - - border: 1px solid #d3d3d3; - box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); -} - -div.jsoneditor-actionmenu.jsoneditor-actionmenu-top { - top: auto; - bottom: 20px; -} - -div.jsoneditor-modemenu.jsoneditor-modemenu { - top: 26px; - left: 0; -} - -div.jsoneditor-menu-item { - line-height: 0; - font-size: 0; -} - -button.jsoneditor-menu-button { - width: 136px; - height: 24px; - padding: 0; - margin: 0; - line-height: 24px; - - background: transparent; - border: transparent; - display: inline-block; - box-sizing: border-box; - - cursor: pointer; - color: #4d4d4d; - - font-size: 10pt; - font-family: arial, sans-serif; - text-align: left; -} - -button.jsoneditor-menu-button:hover, -button.jsoneditor-menu-button:focus { - color: $black; - background-color: #f5f5f5; - outline: none; -} - -button.jsoneditor-menu-button.jsoneditor-selected { - color: white; - background-color: #ee422e; -} - -button.jsoneditor-menu-default { - width: 104px; /* 136px - 32px */ -} - -button.jsoneditor-menu-expand { - width: 32px; - float: right; - border-left: 1px solid #e5e5e5; -} - -span.jsoneditor-icon { - float: left; - width: 24px; - height: 24px; - border: none; - padding: 0; - margin: 0; - background-image: url('img/jsoneditor-icons.svg'); -} - -span.jsoneditor-icon.jsoneditor-icon-expand { - float: right; - width: 24px; - margin: 0 4px; - - background-position: 0 -72px !important; - opacity: 0.4; -} - -div.jsoneditor-menu-item button.jsoneditor-menu-button:hover span.jsoneditor-icon-expand, -div.jsoneditor-menu-item button:focus span.jsoneditor-icon-expand { - opacity: 1; -} - -span.jsoneditor-text { - display: inline-block; - line-height: 24px; -} - -div.jsoneditor-menu-separator { - height: 0; - border-top: 1px solid #e5e5e5; - padding-top: 5px; - margin-top: 5px; -} +/*********************************** Menu *************************************/ div.jsoneditor-menu-panel-right { float: right; max-width: 100%; } -button.jsoneditor-remove span.jsoneditor-icon { - background-position: -24px -24px; -} -button.jsoneditor-remove:hover span.jsoneditor-icon, -button.jsoneditor-remove:focus span.jsoneditor-icon { - background-position: -24px 0; -} +/******************************* Action Menu **********************************/ -button.jsoneditor-insert span.jsoneditor-icon { - background-position: 0 -24px; -} -button.jsoneditor-insert:hover span.jsoneditor-icon, -button.jsoneditor-insert:focus span.jsoneditor-icon { - background-position: 0 0; -} +// TODO: cleanup old ActionMenu css +//div.jsoneditor-actionmenu { +// position: absolute; +// box-sizing: border-box; +// z-index: 99999; +// top: 20px; +// left: 18px; /* 20px - 2px where 2px half the difference between 24x24 icons of the menu and the 20x20 icons of the editor */ +// background: white; +// +// border: 1px solid #d3d3d3; +// box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); +//} +// +//div.jsoneditor-actionmenu.jsoneditor-actionmenu-top { +// top: auto; +// bottom: 20px; +//} +// +//div.jsoneditor-modemenu.jsoneditor-modemenu { +// top: 26px; +// left: 0; +//} +// +//div.jsoneditor-menu-item { +// line-height: 0; +// font-size: 0; +//} +// +//button.jsoneditor-menu-button { +// width: 136px; +// height: 24px; +// padding: 0; +// margin: 0; +// line-height: 24px; +// +// background: transparent; +// border: transparent; +// display: inline-block; +// box-sizing: border-box; +// +// cursor: pointer; +// color: #4d4d4d; +// +// font-size: 10pt; +// font-family: arial, sans-serif; +// text-align: left; +//} +// +//button.jsoneditor-menu-button:hover, +//button.jsoneditor-menu-button:focus { +// color: $black; +// background-color: #f5f5f5; +// outline: none; +//} +// +//button.jsoneditor-menu-button.jsoneditor-selected { +// color: white; +// background-color: #ee422e; +//} +// +//button.jsoneditor-menu-default { +// width: 104px; /* 136px - 32px */ +//} +// +//button.jsoneditor-menu-expand { +// width: 32px; +// float: right; +// border-left: 1px solid #e5e5e5; +//} +// +//span.jsoneditor-icon { +// float: left; +// width: 24px; +// height: 24px; +// border: none; +// padding: 0; +// margin: 0; +// background-image: url('img/jsoneditor-icons.svg'); +//} +// +//span.jsoneditor-icon.jsoneditor-icon-expand { +// float: right; +// width: 24px; +// margin: 0 4px; +// +// background-position: 0 -72px !important; +// opacity: 0.4; +//} +// +//div.jsoneditor-menu-item button.jsoneditor-menu-button:hover span.jsoneditor-icon-expand, +//div.jsoneditor-menu-item button:focus span.jsoneditor-icon-expand { +// opacity: 1; +//} +// +//span.jsoneditor-text { +// display: inline-block; +// line-height: 24px; +//} +// +//div.jsoneditor-menu-separator { +// height: 0; +// border-top: 1px solid #e5e5e5; +// padding-top: 5px; +// margin-top: 5px; +//} +// +//button.jsoneditor-remove span.jsoneditor-icon { +// background-position: -24px -24px; +//} +//button.jsoneditor-remove:hover span.jsoneditor-icon, +//button.jsoneditor-remove:focus span.jsoneditor-icon { +// background-position: -24px 0; +//} +// +//button.jsoneditor-insert span.jsoneditor-icon { +// background-position: 0 -24px; +//} +//button.jsoneditor-insert:hover span.jsoneditor-icon, +//button.jsoneditor-insert:focus span.jsoneditor-icon { +// background-position: 0 0; +//} +// +//button.jsoneditor-duplicate span.jsoneditor-icon { +// background-position: -48px -24px; +//} +//button.jsoneditor-duplicate:hover span.jsoneditor-icon, +//button.jsoneditor-duplicate:focus span.jsoneditor-icon { +// background-position: -48px 0; +//} +// +//button.jsoneditor-sort-asc span.jsoneditor-icon { +// background-position: -168px -24px; +//} +//button.jsoneditor-sort-asc:hover span.jsoneditor-icon, +//button.jsoneditor-sort-asc:focus span.jsoneditor-icon { +// background-position: -168px 0; +//} +// +//button.jsoneditor-sort-desc span.jsoneditor-icon { +// background-position: -192px -24px; +//} +//button.jsoneditor-sort-desc:hover span.jsoneditor-icon, +//button.jsoneditor-sort-desc:focus span.jsoneditor-icon { +// background-position: -192px 0; +//} +// +//div.jsoneditor-submenu { +// visibility: hidden; +// max-height: 0; +// +// overflow: hidden; +// +// transition: max-height 0.3s ease-out; +// +// box-shadow: inset 0 10px 10px -10px rgba(128, 128, 128, 0.5), +// inset 0 -10px 10px -10px rgba(128, 128, 128, 0.5) +//} +// +//div.jsoneditor-submenu.jsoneditor-expanded { +// visibility: visible; +// max-height: 104px; /* 4 * 24px + 2 * 5px */ +// /* FIXME: shouldn't rely on max-height equal to 4 items, should be flexible */ +//} +// +//div.jsoneditor-submenu.jsoneditor-collapsing { +// visibility: visible; +// max-height: 0; +//} +// +//div.jsoneditor-submenu button { +// padding-left: 24px; +//} +// +//div.jsoneditor-submenu div.jsoneditor-menu-item:first-child { +// margin-top: 5px; +//} +// +//div.jsoneditor-submenu div.jsoneditor-menu-item:last-child { +// margin-bottom: 5px; +//} +// +//button.jsoneditor-type-string span.jsoneditor-icon { +// background-position: -144px -24px; +//} +//button.jsoneditor-type-string:hover span.jsoneditor-icon, +//button.jsoneditor-type-string:focus span.jsoneditor-icon, +//button.jsoneditor-type-string.jsoneditor-selected span.jsoneditor-icon { +// background-position: -144px 0; +//} +// +//button.jsoneditor-type-value span.jsoneditor-icon { +// background-position: -120px -24px; +//} +//button.jsoneditor-type-value:hover span.jsoneditor-icon, +//button.jsoneditor-type-value:focus span.jsoneditor-icon, +//button.jsoneditor-type-value.jsoneditor-selected span.jsoneditor-icon { +// background-position: -120px 0; +//} +// +//button.jsoneditor-type-Object span.jsoneditor-icon { +// background-position: -72px -24px; +//} +//button.jsoneditor-type-Object:hover span.jsoneditor-icon, +//button.jsoneditor-type-Object:focus span.jsoneditor-icon, +//button.jsoneditor-type-Object.jsoneditor-selected span.jsoneditor-icon { +// background-position: -72px 0; +//} +// +//button.jsoneditor-type-Array span.jsoneditor-icon { +// background-position: -96px -24px; +//} +//button.jsoneditor-type-Array:hover span.jsoneditor-icon, +//button.jsoneditor-type-Array:focus span.jsoneditor-icon, +//button.jsoneditor-type-Array.jsoneditor-selected span.jsoneditor-icon { +// background-position: -96px 0; +//} -button.jsoneditor-duplicate span.jsoneditor-icon { - background-position: -48px -24px; -} -button.jsoneditor-duplicate:hover span.jsoneditor-icon, -button.jsoneditor-duplicate:focus span.jsoneditor-icon { - background-position: -48px 0; -} - -button.jsoneditor-sort-asc span.jsoneditor-icon { - background-position: -168px -24px; -} -button.jsoneditor-sort-asc:hover span.jsoneditor-icon, -button.jsoneditor-sort-asc:focus span.jsoneditor-icon { - background-position: -168px 0; -} - -button.jsoneditor-sort-desc span.jsoneditor-icon { - background-position: -192px -24px; -} -button.jsoneditor-sort-desc:hover span.jsoneditor-icon, -button.jsoneditor-sort-desc:focus span.jsoneditor-icon { - background-position: -192px 0; -} - -div.jsoneditor-submenu { - visibility: hidden; - max-height: 0; - - overflow: hidden; - - transition: max-height 0.3s ease-out; - - box-shadow: inset 0 10px 10px -10px rgba(128, 128, 128, 0.5), - inset 0 -10px 10px -10px rgba(128, 128, 128, 0.5) -} - -div.jsoneditor-submenu.jsoneditor-expanded { - visibility: visible; - max-height: 104px; /* 4 * 24px + 2 * 5px */ - /* FIXME: shouldn't rely on max-height equal to 4 items, should be flexible */ -} - -div.jsoneditor-submenu.jsoneditor-collapsing { - visibility: visible; - max-height: 0; -} - -div.jsoneditor-submenu button { - padding-left: 24px; -} - -div.jsoneditor-submenu div.jsoneditor-menu-item:first-child { - margin-top: 5px; -} - -div.jsoneditor-submenu div.jsoneditor-menu-item:last-child { - margin-bottom: 5px; -} - -button.jsoneditor-type-string span.jsoneditor-icon { - background-position: -144px -24px; -} -button.jsoneditor-type-string:hover span.jsoneditor-icon, -button.jsoneditor-type-string:focus span.jsoneditor-icon, -button.jsoneditor-type-string.jsoneditor-selected span.jsoneditor-icon { - background-position: -144px 0; -} - -button.jsoneditor-type-value span.jsoneditor-icon { - background-position: -120px -24px; -} -button.jsoneditor-type-value:hover span.jsoneditor-icon, -button.jsoneditor-type-value:focus span.jsoneditor-icon, -button.jsoneditor-type-value.jsoneditor-selected span.jsoneditor-icon { - background-position: -120px 0; -} - -button.jsoneditor-type-Object span.jsoneditor-icon { - background-position: -72px -24px; -} -button.jsoneditor-type-Object:hover span.jsoneditor-icon, -button.jsoneditor-type-Object:focus span.jsoneditor-icon, -button.jsoneditor-type-Object.jsoneditor-selected span.jsoneditor-icon { - background-position: -72px 0; -} - -button.jsoneditor-type-Array span.jsoneditor-icon { - background-position: -96px -24px; -} -button.jsoneditor-type-Array:hover span.jsoneditor-icon, -button.jsoneditor-type-Array:focus span.jsoneditor-icon, -button.jsoneditor-type-Array.jsoneditor-selected span.jsoneditor-icon { - background-position: -96px 0; -} - -/******************************* Floatting Menu **********************************/ +/******************************* Floating Menu **********************************/ div.jsoneditor-node-container { position: relative; transition: background-color 100ms ease-in; - // TODO: can the hover/select css be simplified? - + // TODO: cleanup insert-area css? div.jsoneditor-insert-area { //background: gray; position: absolute; @@ -656,6 +676,34 @@ div.jsoneditor-node-container { } } + div.jsoneditor-insert { + width: $line-height; + height: $line-height; + + &:hover { + background: url('img/jsoneditor-icons.svg') -2px -26px; + } + } + + &.jsoneditor-selected-insert-after { + border-bottom: 1px dashed $light-gray; + margin-bottom: -1px; + + > .jsoneditor-node > .jsoneditor-insert-after, + > .jsoneditor-node-end > .jsoneditor-insert-after { + background-image: url('img/jsoneditor-icons.svg'); + background-position: -2px -2px !important; + } + } + + &.jsoneditor-selected-insert-before { + > .jsoneditor-node > .jsoneditor-insert-before, + > .jsoneditor-node-end > .jsoneditor-insert-before { + background-image: url('img/jsoneditor-icons.svg'); + background-position: -2px -2px !important; + } + } + &.jsoneditor-hover { > .jsoneditor-node > .jsoneditor-button-container, > .jsoneditor-node > .jsoneditor-button-placeholder { diff --git a/src/jsoneditor/eson.js b/src/jsoneditor/eson.js index b8b6404..bc484c6 100644 --- a/src/jsoneditor/eson.js +++ b/src/jsoneditor/eson.js @@ -19,6 +19,8 @@ export const SELECTED_FIRST = 8 export const SELECTED_LAST = 16 export const SELECTED_BEFORE = 32 export const SELECTED_AFTER = 64 +export const SELECTED_EMPTY = 128 +export const SELECTED_EMPTY_BEFORE = 256 export const META = Symbol('meta') @@ -361,14 +363,24 @@ export function applySelection (eson, selection) { } else if (selection.before) { const updatedEson = setIn(eson, selection.before.concat([META, 'selected']), - SELECTED + SELECTED_BEFORE) + SELECTED_BEFORE) return cleanupMetaData(updatedEson, 'selected', [selection.before]) } else if (selection.after) { const updatedEson = setIn(eson, selection.after.concat([META, 'selected']), - SELECTED + SELECTED_AFTER) + SELECTED_AFTER) return cleanupMetaData(updatedEson, 'selected', [selection.after]) } + else if (selection.empty) { + const updatedEson = setIn(eson, selection.empty.concat([META, 'selected']), + SELECTED_EMPTY) + return cleanupMetaData(updatedEson, 'selected', [selection.empty]) + } + else if (selection.emptyBefore) { + const updatedEson = setIn(eson, selection.emptyBefore.concat([META, 'selected']), + SELECTED_EMPTY_BEFORE) + return cleanupMetaData(updatedEson, 'selected', [selection.emptyBefore]) + } else { // selection.start and selection.end // find the parent node shared by both start and end of the selection const rootPath = findRootPath(selection) @@ -556,6 +568,12 @@ export function findRootPath(selection) { else if (selection.after) { return initial(selection.after) } + else if (selection.empty) { + return initial(selection.empty) + } + else if (selection.emptyBefore) { + return initial(selection.emptyBefore) + } else { // selection.start and selection.end const sharedPath = findSharedPath(selection.start, selection.end) diff --git a/src/jsoneditor/types.js b/src/jsoneditor/types.js index 7b12c45..c4080a5 100644 --- a/src/jsoneditor/types.js +++ b/src/jsoneditor/types.js @@ -42,7 +42,7 @@ */ /** - * @typedef {'value' | 'property'} ESONPointerArea + * @typedef {'value' | 'property' | 'before' | 'after' | 'empty'} ESONPointerArea */ /**