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