From 56327b8f5630866a40cea64de283d8d050d94661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20K=C5=82os?= Date: Mon, 30 Oct 2023 09:48:07 +0100 Subject: [PATCH] jsdialog: open submenu on hover in dropdowns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showsubmenu event on hover - hidedropdown event on leave - submenu is inserted into the same overlay as parent dropdown - don't destory overlay if submenu is closed, do that only for root - works on tablet / touch device Signed-off-by: Szymon Kłos Change-Id: Ibb4a747555e172dd4e040950cdfd309560f214a7 --- browser/css/jsdialogs.css | 5 +++++ browser/src/control/Control.JSDialog.js | 16 ++++++++++++-- .../src/control/Control.NotebookbarBuilder.js | 22 +++++++++---------- browser/src/control/jsdialog/Util.Dropdown.js | 22 +++++++++++++++---- .../src/control/jsdialog/Widget.Combobox.js | 10 ++++++++- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/browser/css/jsdialogs.css b/browser/css/jsdialogs.css index 9dbcc541b0ce..88243fa0291c 100644 --- a/browser/css/jsdialogs.css +++ b/browser/css/jsdialogs.css @@ -797,6 +797,11 @@ input[type='number']:hover::-webkit-outer-spin-button { border-radius: var(--border-radius); } +.modalpopup[id$='-dropdown'] { + /* submenu is inserted into the same overlay, dont make it relative to parent */ + position: absolute !important; +} + .modalpopup[id$='-dropdown'] .ui-dialog-content { padding: 0px; } diff --git a/browser/src/control/Control.JSDialog.js b/browser/src/control/Control.JSDialog.js index f0a79d369cad..d3e2c16def40 100644 --- a/browser/src/control/Control.JSDialog.js +++ b/browser/src/control/Control.JSDialog.js @@ -61,7 +61,7 @@ L.Control.JSDialog = L.Control.extend({ L.DomUtil.remove(this.dialogs[id].container); - if (this.dialogs[id].overlay) + if (this.dialogs[id].overlay && !this.dialogs[id].isSubmenu) L.DomUtil.remove(this.dialogs[id].overlay); delete this.dialogs[id]; @@ -71,7 +71,7 @@ L.Control.JSDialog = L.Control.extend({ close: function(id, sendCloseEvent) { if (id && this.dialogs[id]) { - if (!sendCloseEvent && this.dialogs[id].overlay) + if (!sendCloseEvent && this.dialogs[id].overlay && !this.dialogs[id].isSubmenu) L.DomUtil.remove(this.dialogs[id].overlay); if (this.dialogs[id].timeoutId) @@ -201,6 +201,12 @@ L.Control.JSDialog = L.Control.extend({ }, getOrCreateOverlay: function(instance) { + // Submenu is created inside the same overlay as parent dropdown + if (instance.isDropdown && instance.isSubmenu) { + instance.overlay = document.body.querySelector('.jsdialog-overlay'); + return; + } + // Dialogue overlay which will allow automatic positioning and cancellation of the dialogue if cancellable. var overlay = L.DomUtil.get(instance.id + '-overlay'); if (!overlay) { @@ -379,6 +385,12 @@ L.Control.JSDialog = L.Control.extend({ else console.error('cannot get focus for widget: "' + instance.init_focus_id + '"'); } + + if (instance.isDropdown && instance.isSubmenu) { + instance.container.addEventListener('mouseleave', function () { + instance.builder.callback('combobox', 'hidedropdown', {id: instance.id}, null, instance.builder); + }); + } }, setPosition: function(instance, updatedPos) { diff --git a/browser/src/control/Control.NotebookbarBuilder.js b/browser/src/control/Control.NotebookbarBuilder.js index b02004970b4d..23c03d3e0f04 100644 --- a/browser/src/control/Control.NotebookbarBuilder.js +++ b/browser/src/control/Control.NotebookbarBuilder.js @@ -842,19 +842,17 @@ L.Control.NotebookbarBuilder = L.Control.JSDialogBuilder.extend({ var dropdownId = data.id; var clickFunction = function () { var callback = function(objectType, eventType, object, data, entry) { - if (eventType === 'selected') { + if ((eventType === 'selected' || eventType === 'showsubmenu') && entry.id) { var pos = data.substr(0, parseInt(data.indexOf(';'))); - if (entry.id) { - var subDropdownId = dropdownId + '-' + pos; - var dropdown = JSDialog.GetDropdown(subDropdownId); - var container = dropdown.querySelector('.ui-grid'); - container.innerHTML = getMenuHtml(entry.id); - JSDialog.MakeFocusCycle(container); - JSDialog.GetFocusableElements(container)[0].focus(); - } else if (entry.uno) { - builder.map.sendUnoCommand(entry.uno); - JSDialog.CloseDropdown(dropdownId); - } + var subDropdownId = dropdownId + '-' + pos; + var dropdown = JSDialog.GetDropdown(subDropdownId); + var container = dropdown.querySelector('.ui-grid'); + container.innerHTML = getMenuHtml(entry.id); + JSDialog.MakeFocusCycle(container); + JSDialog.GetFocusableElements(container)[0].focus(); + } else if (eventType === 'selected' && entry.uno) { + builder.map.sendUnoCommand(entry.uno); + JSDialog.CloseDropdown(dropdownId); } return true; diff --git a/browser/src/control/jsdialog/Util.Dropdown.js b/browser/src/control/jsdialog/Util.Dropdown.js index 1559848dc4f9..29c9a137ee19 100644 --- a/browser/src/control/jsdialog/Util.Dropdown.js +++ b/browser/src/control/jsdialog/Util.Dropdown.js @@ -15,11 +15,12 @@ function _createDropdownId(id) { return id + '-dropdown'; } -JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popupAnchor) { +JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popupAnchor, isSubmenu) { var dropdownWindowId = _createDropdownId(id); var json = { id: dropdownWindowId, type: 'dropdown', + isSubmenu: isSubmenu, jsontype: 'dialog', popupParent: popupParent, popupAnchor: popupAnchor, @@ -58,17 +59,30 @@ JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popup json.children[0].children.push(entry); } + var lastSubMenuOpened = null; var generateCallback = function (targetEntries) { return function(objectType, eventType, object, data) { - if (eventType === 'selected') { + if (eventType === 'selected' || eventType === 'showsubmenu') { var pos = parseInt(data.substr(0, data.indexOf(';'))); if (targetEntries[pos].items) { + if (lastSubMenuOpened) { + var submenu = JSDialog.GetDropdown(lastSubMenuOpened); + if (submenu) { + JSDialog.CloseDropdown(lastSubMenuOpened); + lastSubMenuOpened = null; + } + } + // open submenu var dropdown = JSDialog.GetDropdown(object.id); + var subMenuId = object.id + '-' + pos; var targetEntry = dropdown.querySelectorAll('.ui-grid-cell')[pos + 1]; - JSDialog.OpenDropdown(object.id + '-' + pos, targetEntry, targetEntries[pos].items, - generateCallback(targetEntries[pos].items), 'top-end'); + JSDialog.OpenDropdown(subMenuId, targetEntry, targetEntries[pos].items, + generateCallback(targetEntries[pos].items), 'top-end', true); + lastSubMenuOpened = subMenuId; } + } else if (!lastSubMenuOpened && eventType === 'hidedropdown') { + JSDialog.CloseDropdown(id); } // for multi-level menus last parameter should be used to handle event (it contains selected entry) diff --git a/browser/src/control/jsdialog/Widget.Combobox.js b/browser/src/control/jsdialog/Widget.Combobox.js index cdd4a2ed956b..2c47e7614c58 100644 --- a/browser/src/control/jsdialog/Widget.Combobox.js +++ b/browser/src/control/jsdialog/Widget.Combobox.js @@ -68,8 +68,10 @@ JSDialog.comboboxEntry = function (parentContainer, data, builder) { } } + var entryData = data.pos + ';' + data.text; + var clickFunction = function () { - builder.callback('combobox', 'selected', {id: data.comboboxId}, data.pos + ';' + data.text, builder); + builder.callback('combobox', 'selected', {id: data.comboboxId}, entryData, builder); }; entry.addEventListener('click', clickFunction); @@ -80,6 +82,12 @@ JSDialog.comboboxEntry = function (parentContainer, data, builder) { } }); + if (data.hasSubMenu) { + entry.addEventListener('mouseover', function () { + builder.callback('combobox', 'showsubmenu', {id: data.comboboxId}, entryData, builder); + }); + } + return false; };