diff --git a/HISTORY.md b/HISTORY.md index eccff1b..f38859b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,7 @@ https://github.com/josdejong/jsoneditor ## 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 with bootstrap. See #791. Thanks @dmitry-kulikov. - Fixed react examples not working out of the box when cloning or downloading diff --git a/src/js/Node.js b/src/js/Node.js index 2b07fba..1db0a25 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -288,32 +288,49 @@ export class Node { 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') button.type = 'button' button.className = 'jsoneditor-button jsoneditor-schema-error' - button.appendChild(popover) - // update the direction of the popover - button.onmouseover = button.onfocus = function updateDirection () { - const directions = ['right', 'above', 'below', 'left'] - 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 - } + const destroy = () => { + if (this.dom.popupAnchor) { + this.dom.popupAnchor.destroy() // this will trigger the onDestroy callback } - }.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 // child node, and set focus to the child node @@ -861,6 +878,11 @@ export class Node { if (table) { table.removeChild(tr) } + + if (this.dom.popupAnchor) { + this.dom.popupAnchor.destroy() + } + this.hideChilds(options) } diff --git a/src/js/createAbsoluteAnchor.js b/src/js/createAbsoluteAnchor.js index 6ce516b..a6ec62d 100644 --- a/src/js/createAbsoluteAnchor.js +++ b/src/js/createAbsoluteAnchor.js @@ -5,10 +5,11 @@ import { isChildOf, removeEventListener, addEventListener } from './util' * element. * @param {HTMLElement} anchor * @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} */ -export function createAbsoluteAnchor (anchor, parent, onDestroy) { +export function createAbsoluteAnchor (anchor, parent, onDestroy, destroyOnMouseOut = false) { const root = getRootNode(anchor) 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 - const destroyIfOutside = event => { - const target = event.target - if ((target !== absoluteAnchor) && !isChildOf(target, absoluteAnchor)) { + function destroyIfOutside (event) { + if (isOutside(event.target)) { destroy() } } eventListeners.mousedown = addEventListener(root, 'mousedown', 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 diff --git a/src/scss/jsoneditor.scss b/src/scss/jsoneditor.scss index 616695b..3fa70c6 100644 --- a/src/scss/jsoneditor.scss +++ b/src/scss/jsoneditor.scss @@ -82,6 +82,8 @@ div { } div { &.jsoneditor-anchor { + cursor: pointer; + .picker_wrapper { &.popup { &.popup_bottom { @@ -370,12 +372,14 @@ div.jsoneditor-value, div.jsoneditor td, div.jsoneditor th, div.jsoneditor textarea, -div.jsoneditor pre.jsoneditor-preview, -div.jsoneditor .jsoneditor-schema-error { +pre.jsoneditor-preview, +.jsoneditor-schema-error, +.jsoneditor-popover{ font-family: $jse-font-mono; font-size: $jse-font-size; color: $jse-content-color; } + .jsoneditor-schema-error { cursor: default; display: inline-block; @@ -384,74 +388,76 @@ div.jsoneditor .jsoneditor-schema-error { position: relative; text-align: center; width: 24px; - .jsoneditor-popover { - background-color: $jse-popover-bg; - border-radius: 3px; - box-shadow: $jse-box-shadow-sm; - color: $jse-white; - display: none; - padding: 7px 10px; - position: absolute; - width: 200px; - z-index: 4; - &.jsoneditor-above { - bottom: 32px; - left: -98px; - &:before { - border-top: 7px solid $jse-popover-bg; - 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; - } - } +} + +.jsoneditor-popover { + background-color: $jse-popover-bg; + border-radius: 3px; + box-shadow: $jse-box-shadow-sm; + color: $jse-white; + padding: 7px 10px; + position: absolute; + cursor: auto; + width: 200px; + z-index: 999; + &.jsoneditor-above { + bottom: 32px; + left: -98px; &:before { - border-right: 7px solid transparent; - border-left: 7px solid transparent; + border-top: 7px solid $jse-popover-bg; + 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: ""; - display: block; - left: 50%; - margin-left: -7px; + 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 { + border-right: 7px solid transparent; + border-left: 7px solid transparent; + content: ""; + display: block; + left: 50%; + margin-left: -7px; + position: absolute; + } } + .jsoneditor-text-errors { tr { &.jump-to-line { diff --git a/test/test_schema.html b/test/test_schema.html index babd22b..77a668f 100644 --- a/test/test_schema.html +++ b/test/test_schema.html @@ -15,12 +15,19 @@ padding-left: 40px; } + html, body { + width: 100%; + padding: 0; + box-sizing: border-box; + } + code { background-color: #f5f5f5; } #jsoneditor { - width: 600px; + max-width: 600px; + width: 90%; height: 500px; }