Fixed #104: context menus are now positioned relative to the elements of the editor instead of an absolute position in the window
This commit is contained in:
parent
5955111b4d
commit
6b1c75b2c2
|
@ -30,6 +30,8 @@ https://github.com/josdejong/jsoneditor
|
|||
- Fixed #38: clear search results after a new JSON object is set.
|
||||
- Fixed #242: row stays highlighted when dragging outside editor.
|
||||
- Fixed quick-keys Shift+Alt+Arrows not registering actions in history.
|
||||
- Fixed #104: context menus are now positioned relative to the elements of the
|
||||
editor instead of an absolute position in the window.
|
||||
|
||||
|
||||
## 2015-06-13, version 4.2.1
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
|
||||
/* ContextMenu - main menu */
|
||||
|
||||
div.jsoneditor-contextmenu-root {
|
||||
position: relative;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu {
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
|
|
|
@ -142,7 +142,7 @@ div.jsoneditor-tree button.jsoneditor-contextmenu {
|
|||
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu:hover,
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu:focus,
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu.selected,
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected,
|
||||
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
|
||||
background-position: -48px -48px;
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ div.jsoneditor {
|
|||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
line-height: 100%;
|
||||
|
@ -197,8 +197,6 @@ div.jsoneditor-outer {
|
|||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree {
|
||||
|
|
|
@ -4,7 +4,6 @@ div.jsoneditor-menu {
|
|||
height: 35px;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
@ -14,7 +13,8 @@ div.jsoneditor-menu {
|
|||
border-bottom: 1px solid #3883fa;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button {
|
||||
div.jsoneditor-menu > button,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 2px;
|
||||
|
@ -31,15 +31,19 @@ div.jsoneditor-menu > button {
|
|||
float: left;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button:hover {
|
||||
div.jsoneditor-menu > button:hover,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:hover {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
border: 1px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
div.jsoneditor-menu > button:focus,
|
||||
div.jsoneditor-menu > button:active {
|
||||
div.jsoneditor-menu > button:active,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:focus,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:active {
|
||||
background-color: rgba(255,255,255,0.3);
|
||||
}
|
||||
div.jsoneditor-menu > button:disabled {
|
||||
div.jsoneditor-menu > button:disabled,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
@ -68,14 +72,20 @@ div.jsoneditor-menu > button.jsoneditor-format {
|
|||
background-position: -72px -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-modes {
|
||||
div.jsoneditor-menu > div.jsoneditor-modes {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button {
|
||||
background-image: none;
|
||||
width: auto;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-separator {
|
||||
div.jsoneditor-menu > button.jsoneditor-separator,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
@ -98,5 +108,3 @@ div.jsoneditor-menu a.jsoneditor-poweredBy {
|
|||
top: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* TODO: css for button:disabled is not supported by IE8 */
|
||||
|
|
|
@ -18,13 +18,18 @@ function ContextMenu (items, options) {
|
|||
this.items = items;
|
||||
this.eventListeners = {};
|
||||
this.selection = undefined; // holds the selection before the menu was opened
|
||||
this.visibleSubmenu = undefined;
|
||||
this.onClose = options ? options.close : undefined;
|
||||
|
||||
// create root element
|
||||
var root = document.createElement('div');
|
||||
root.className = 'jsoneditor-contextmenu-root';
|
||||
dom.root = root;
|
||||
|
||||
// create a container element
|
||||
var menu = document.createElement('div');
|
||||
menu.className = 'jsoneditor-contextmenu';
|
||||
dom.menu = menu;
|
||||
root.appendChild(menu);
|
||||
|
||||
// create a list to hold the menu items
|
||||
var list = document.createElement('ul');
|
||||
|
@ -176,60 +181,65 @@ ContextMenu.visibleMenu = undefined;
|
|||
|
||||
/**
|
||||
* Attach the menu to an anchor
|
||||
* @param {HTMLElement} anchor
|
||||
* @param {HTMLElement} anchor Anchor where the menu will be attached
|
||||
* as sibling.
|
||||
* @param {HTMLElement} [contentWindow] The DIV with with the (scrollable) contents
|
||||
*/
|
||||
ContextMenu.prototype.show = function (anchor) {
|
||||
ContextMenu.prototype.show = function (anchor, contentWindow) {
|
||||
this.hide();
|
||||
|
||||
// calculate whether the menu fits below the anchor
|
||||
var windowHeight = window.innerHeight,
|
||||
windowScroll = (window.pageYOffset || document.scrollTop || 0),
|
||||
windowBottom = windowHeight + windowScroll,
|
||||
anchorHeight = anchor.offsetHeight,
|
||||
menuHeight = this.maxHeight;
|
||||
// determine whether to display the menu below or above the anchor
|
||||
var showBelow = true;
|
||||
if (contentWindow) {
|
||||
var anchorRect = anchor.getBoundingClientRect();
|
||||
var contentRect = contentWindow.getBoundingClientRect();
|
||||
|
||||
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
|
||||
// fits below -> show below
|
||||
}
|
||||
else if (anchorRect.top - this.maxHeight > contentRect.top) {
|
||||
// fits above -> show above
|
||||
showBelow = false;
|
||||
}
|
||||
else {
|
||||
// doesn't fit above nor below -> show below
|
||||
}
|
||||
}
|
||||
|
||||
// position the menu
|
||||
var left = util.getAbsoluteLeft(anchor);
|
||||
var top = util.getAbsoluteTop(anchor);
|
||||
if (top + anchorHeight + menuHeight < windowBottom) {
|
||||
if (showBelow) {
|
||||
// display the menu below the anchor
|
||||
this.dom.menu.style.left = left + 'px';
|
||||
this.dom.menu.style.top = (top + anchorHeight) + 'px';
|
||||
var anchorHeight = anchor.offsetHeight;
|
||||
this.dom.menu.style.left = '0px';
|
||||
this.dom.menu.style.top = anchorHeight + 'px';
|
||||
this.dom.menu.style.bottom = '';
|
||||
}
|
||||
else {
|
||||
// display the menu above the anchor
|
||||
this.dom.menu.style.left = left + 'px';
|
||||
this.dom.menu.style.left = '0px';
|
||||
this.dom.menu.style.top = '';
|
||||
this.dom.menu.style.bottom = (windowHeight - top) + 'px';
|
||||
this.dom.menu.style.bottom = '0px';
|
||||
}
|
||||
|
||||
// attach the menu to the document
|
||||
document.body.appendChild(this.dom.menu);
|
||||
// attach the menu to the parent of the anchor
|
||||
var parent = anchor.parentNode;
|
||||
parent.insertBefore(this.dom.root, parent.firstChild);
|
||||
|
||||
// create and attach event listeners
|
||||
var me = this;
|
||||
var list = this.dom.list;
|
||||
this.eventListeners.mousedown = util.addEventListener(
|
||||
document, 'mousedown', function (event) {
|
||||
// hide menu on click outside of the menu
|
||||
var target = event.target;
|
||||
if ((target != list) && !me._isChildOf(target, list)) {
|
||||
me.hide();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
this.eventListeners.mousewheel = util.addEventListener(
|
||||
document, 'mousewheel', function (event) {
|
||||
// block scrolling when context menu is visible
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
});
|
||||
this.eventListeners.keydown = util.addEventListener(
|
||||
document, 'keydown', function (event) {
|
||||
me._onKeyDown(event);
|
||||
});
|
||||
this.eventListeners.mousedown = util.addEventListener(window, 'mousedown', function (event) {
|
||||
// hide menu on click outside of the menu
|
||||
var target = event.target;
|
||||
if ((target != list) && !me._isChildOf(target, list)) {
|
||||
me.hide();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
this.eventListeners.keydown = util.addEventListener(window, 'keydown', function (event) {
|
||||
me._onKeyDown(event);
|
||||
});
|
||||
|
||||
// move focus to the first button in the context menu
|
||||
this.selection = util.getSelection();
|
||||
|
@ -249,8 +259,8 @@ ContextMenu.prototype.show = function (anchor) {
|
|||
*/
|
||||
ContextMenu.prototype.hide = function () {
|
||||
// remove the menu from the DOM
|
||||
if (this.dom.menu.parentNode) {
|
||||
this.dom.menu.parentNode.removeChild(this.dom.menu);
|
||||
if (this.dom.root.parentNode) {
|
||||
this.dom.root.parentNode.removeChild(this.dom.root);
|
||||
if (this.onClose) {
|
||||
this.onClose();
|
||||
}
|
||||
|
@ -262,7 +272,7 @@ ContextMenu.prototype.hide = function () {
|
|||
if (this.eventListeners.hasOwnProperty(name)) {
|
||||
var fn = this.eventListeners[name];
|
||||
if (fn) {
|
||||
util.removeEventListener(document, name, fn);
|
||||
util.removeEventListener(window, name, fn);
|
||||
}
|
||||
delete this.eventListeners[name];
|
||||
}
|
||||
|
|
|
@ -2786,7 +2786,8 @@ Node.TYPE_TITLES = {
|
|||
|
||||
/**
|
||||
* Show a contextmenu for this node
|
||||
* @param {HTMLElement} anchor Anchor element to attache the context menu to.
|
||||
* @param {HTMLElement} anchor Anchor element to attach the context menu to
|
||||
* as sibling.
|
||||
* @param {function} [onClose] Callback method called when the context menu
|
||||
* is being closed.
|
||||
*/
|
||||
|
@ -2996,7 +2997,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
|
|||
}
|
||||
|
||||
var menu = new ContextMenu(items, {close: onClose});
|
||||
menu.show(anchor);
|
||||
menu.show(anchor, this.editor.content);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -179,7 +179,7 @@ function appendNodeFactory(Node) {
|
|||
];
|
||||
|
||||
var menu = new ContextMenu(items, {close: onClose});
|
||||
menu.show(anchor);
|
||||
menu.show(anchor, this.editor.content);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,7 +94,12 @@ function createModeSwitcher(editor, modes, current) {
|
|||
menu.show(box);
|
||||
};
|
||||
|
||||
return box;
|
||||
var div = document.createElement('div');
|
||||
div.className = 'jsoneditor-modes';
|
||||
div.style.position = 'relative';
|
||||
div.appendChild(box);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
exports.create = createModeSwitcher;
|
||||
|
|
|
@ -653,7 +653,7 @@ treemode._onEvent = function (event) {
|
|||
if (node && node.selected) {
|
||||
if (event.type == 'click') {
|
||||
if (event.target == node.dom.menu) {
|
||||
this.showContextMenu(event.target);
|
||||
this.showContextMenu(event.target.parentNode);
|
||||
|
||||
// stop propagation (else we will open the context menu of a single node)
|
||||
return;
|
||||
|
@ -701,6 +701,10 @@ treemode._startDragDistance = function (event) {
|
|||
};
|
||||
|
||||
treemode._updateDragDistance = function (event) {
|
||||
if (!this.dragDistanceEvent) {
|
||||
this._startDragDistance(event);
|
||||
}
|
||||
|
||||
var diffX = event.pageX - this.dragDistanceEvent.initialPageX;
|
||||
var diffY = event.pageY - this.dragDistanceEvent.initialPageY;
|
||||
|
||||
|
@ -1024,7 +1028,7 @@ treemode.showContextMenu = function (anchor, onClose) {
|
|||
});
|
||||
|
||||
var menu = new ContextMenu(items, {close: onClose});
|
||||
menu.show(anchor);
|
||||
menu.show(anchor, this.content);
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue