Fixed #723: schema error popup not always fully visible

This commit is contained in:
jos 2019-09-11 15:59:19 +02:00
parent e23eb8f191
commit 83e9e0655c
5 changed files with 146 additions and 92 deletions

View File

@ -5,6 +5,7 @@ https://github.com/josdejong/jsoneditor
## not yet published, version 7.0.4 ## not yet published, version 7.0.4
- Fixed #723: schema error popup not always fully visible.
- Fixed wrong text color in search box when using JSONEditor in combination - Fixed wrong text color in search box when using JSONEditor in combination
with bootstrap. See #791. Thanks @dmitry-kulikov. with bootstrap. See #791. Thanks @dmitry-kulikov.
- Fixed react examples not working out of the box when cloning or downloading - Fixed react examples not working out of the box when cloning or downloading

View File

@ -288,32 +288,49 @@ export class Node {
this.dom.tdValue.parentNode.appendChild(tdError) this.dom.tdValue.parentNode.appendChild(tdError)
} }
const popover = document.createElement('div')
popover.className = 'jsoneditor-popover jsoneditor-right'
popover.appendChild(document.createTextNode(error.message))
const button = document.createElement('button') const button = document.createElement('button')
button.type = 'button' button.type = 'button'
button.className = 'jsoneditor-button jsoneditor-schema-error' button.className = 'jsoneditor-button jsoneditor-schema-error'
button.appendChild(popover)
// update the direction of the popover const destroy = () => {
button.onmouseover = button.onfocus = function updateDirection () { if (this.dom.popupAnchor) {
const directions = ['right', 'above', 'below', 'left'] this.dom.popupAnchor.destroy() // this will trigger the onDestroy callback
for (let i = 0; i < directions.length; i++) {
const direction = directions[i]
popover.className = 'jsoneditor-popover jsoneditor-' + direction
const contentRect = this.editor.content.getBoundingClientRect()
const popoverRect = popover.getBoundingClientRect()
const margin = 20 // account for a scroll bar
const fit = insideRect(contentRect, popoverRect, margin)
if (fit) {
break
}
} }
}.bind(this) }
const onDestroy = () => {
delete this.dom.popupAnchor
}
const createPopup = (destroyOnMouseOut) => {
const frame = this.editor.frame
this.dom.popupAnchor = createAbsoluteAnchor(button, frame, onDestroy, destroyOnMouseOut)
const popupWidth = 200; // must correspond to what's configured in the CSS
const buttonRect = button.getBoundingClientRect()
const frameRect = frame.getBoundingClientRect()
const position = (frameRect.width - buttonRect.x > (popupWidth / 2 + 20))
? 'jsoneditor-above'
: 'jsoneditor-left'
const popover = document.createElement('div')
popover.className = 'jsoneditor-popover ' + position
popover.appendChild(document.createTextNode(error.message))
this.dom.popupAnchor.appendChild(popover)
}
button.onmouseover = () => {
if (!this.dom.popupAnchor) {
createPopup(true)
}
}
button.onfocus = () => {
destroy()
createPopup(false)
}
button.onblur = () => {
destroy()
}
// when clicking the error icon, expand all nodes towards the invalid // when clicking the error icon, expand all nodes towards the invalid
// child node, and set focus to the child node // child node, and set focus to the child node
@ -861,6 +878,11 @@ export class Node {
if (table) { if (table) {
table.removeChild(tr) table.removeChild(tr)
} }
if (this.dom.popupAnchor) {
this.dom.popupAnchor.destroy()
}
this.hideChilds(options) this.hideChilds(options)
} }

View File

@ -5,10 +5,11 @@ import { isChildOf, removeEventListener, addEventListener } from './util'
* element. * element.
* @param {HTMLElement} anchor * @param {HTMLElement} anchor
* @param {HTMLElement} parent * @param {HTMLElement} parent
* @param [onDestroy(function(anchor)] Callback when the anchor is destroyed * @param {function(HTMLElement)} [onDestroy] Callback when the anchor is destroyed
* @param {boolean} [destroyOnMouseOut=false] If true, anchor will be removed on mouse out
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
export function createAbsoluteAnchor (anchor, parent, onDestroy) { export function createAbsoluteAnchor (anchor, parent, onDestroy, destroyOnMouseOut = false) {
const root = getRootNode(anchor) const root = getRootNode(anchor)
const eventListeners = {} const eventListeners = {}
@ -48,17 +49,34 @@ export function createAbsoluteAnchor (anchor, parent, onDestroy) {
} }
} }
function isOutside (target) {
return (target !== absoluteAnchor) && !isChildOf(target, absoluteAnchor)
}
// create and attach event listeners // create and attach event listeners
const destroyIfOutside = event => { function destroyIfOutside (event) {
const target = event.target if (isOutside(event.target)) {
if ((target !== absoluteAnchor) && !isChildOf(target, absoluteAnchor)) {
destroy() destroy()
} }
} }
eventListeners.mousedown = addEventListener(root, 'mousedown', destroyIfOutside) eventListeners.mousedown = addEventListener(root, 'mousedown', destroyIfOutside)
eventListeners.mousewheel = addEventListener(root, 'mousewheel', destroyIfOutside) eventListeners.mousewheel = addEventListener(root, 'mousewheel', destroyIfOutside)
// eventListeners.scroll = addEventListener(root, 'scroll', destroyIfOutside);
if (destroyOnMouseOut) {
let destroyTimer = null
absoluteAnchor.onmouseover = () => {
clearTimeout(destroyTimer)
destroyTimer = null
}
absoluteAnchor.onmouseout = () => {
if (!destroyTimer) {
destroyTimer = setTimeout(destroy, 200)
}
}
}
absoluteAnchor.destroy = destroy absoluteAnchor.destroy = destroy

View File

@ -82,6 +82,8 @@ div {
} }
div { div {
&.jsoneditor-anchor { &.jsoneditor-anchor {
cursor: pointer;
.picker_wrapper { .picker_wrapper {
&.popup { &.popup {
&.popup_bottom { &.popup_bottom {
@ -370,12 +372,14 @@ div.jsoneditor-value,
div.jsoneditor td, div.jsoneditor td,
div.jsoneditor th, div.jsoneditor th,
div.jsoneditor textarea, div.jsoneditor textarea,
div.jsoneditor pre.jsoneditor-preview, pre.jsoneditor-preview,
div.jsoneditor .jsoneditor-schema-error { .jsoneditor-schema-error,
.jsoneditor-popover{
font-family: $jse-font-mono; font-family: $jse-font-mono;
font-size: $jse-font-size; font-size: $jse-font-size;
color: $jse-content-color; color: $jse-content-color;
} }
.jsoneditor-schema-error { .jsoneditor-schema-error {
cursor: default; cursor: default;
display: inline-block; display: inline-block;
@ -384,74 +388,76 @@ div.jsoneditor .jsoneditor-schema-error {
position: relative; position: relative;
text-align: center; text-align: center;
width: 24px; width: 24px;
.jsoneditor-popover { }
background-color: $jse-popover-bg;
border-radius: 3px; .jsoneditor-popover {
box-shadow: $jse-box-shadow-sm; background-color: $jse-popover-bg;
color: $jse-white; border-radius: 3px;
display: none; box-shadow: $jse-box-shadow-sm;
padding: 7px 10px; color: $jse-white;
position: absolute; padding: 7px 10px;
width: 200px; position: absolute;
z-index: 4; cursor: auto;
&.jsoneditor-above { width: 200px;
bottom: 32px; z-index: 999;
left: -98px; &.jsoneditor-above {
&:before { bottom: 32px;
border-top: 7px solid $jse-popover-bg; left: -98px;
bottom: -7px;
}
}
&.jsoneditor-below {
top: 32px;
left: -98px;
&:before {
border-bottom: 7px solid $jse-popover-bg;
top: -7px;
}
}
&.jsoneditor-left {
top: -7px;
right: 32px;
&:before {
border-left: 7px solid $jse-popover-bg;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: "";
top: 19px;
right: -14px;
left: inherit;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
}
&.jsoneditor-right {
top: -7px;
left: 32px;
&:before {
border-right: 7px solid $jse-popover-bg;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: "";
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
}
&:before { &:before {
border-right: 7px solid transparent; border-top: 7px solid $jse-popover-bg;
border-left: 7px solid transparent; bottom: -7px;
}
}
&.jsoneditor-below {
top: 32px;
left: -98px;
&:before {
border-bottom: 7px solid $jse-popover-bg;
top: -7px;
}
}
&.jsoneditor-left {
top: -7px;
right: 32px;
&:before {
border-left: 7px solid $jse-popover-bg;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: ""; content: "";
display: block; top: 19px;
left: 50%; right: -14px;
margin-left: -7px; left: inherit;
margin-left: inherit;
margin-top: -7px;
position: absolute; position: absolute;
} }
} }
&.jsoneditor-right {
top: -7px;
left: 32px;
&:before {
border-right: 7px solid $jse-popover-bg;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: "";
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
}
&:before {
border-right: 7px solid transparent;
border-left: 7px solid transparent;
content: "";
display: block;
left: 50%;
margin-left: -7px;
position: absolute;
}
} }
.jsoneditor-text-errors { .jsoneditor-text-errors {
tr { tr {
&.jump-to-line { &.jump-to-line {

View File

@ -15,12 +15,19 @@
padding-left: 40px; padding-left: 40px;
} }
html, body {
width: 100%;
padding: 0;
box-sizing: border-box;
}
code { code {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
#jsoneditor { #jsoneditor {
width: 600px; max-width: 600px;
width: 90%;
height: 500px; height: 500px;
} }
</style> </style>