From 45cdcc2be96ca2a16beacd1f6ac0811fb747fbbc Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Fri, 17 Jul 2020 16:18:24 +0300 Subject: [PATCH] refactor(ui5-popover): Keep popup open if focus is inside (#1937) Another PR to address the "QuickCardView" topic. The change keeps the Popover open, without an opener, if the focus is inside that popover. add prevent-focus-restore protected property in addition to the preventFocusRestore param of Popup.prototype.close method, because the quickCardView might close due to user interaction - click, TAB, ESC. remove the recently added _closeWithOpener param, as now the popover without an opener would remain open if the focus is inside it and will be closed otherwise. QuickCardView Interaction (test it on http://localhost:8080/test-resources/pages/Input_quickview.html) Once opened, both Suggestions Popover and QuickCardView will close if: neither the SearchField nor the QuickCardView has the focus, because the user clicks somewhere outside or the user uses the TAB | SHIFT + TAB and the focus is somewhere else Once opened, the QuickCardView remains open and Suggestions Popover closes if: the focus moves to the QuickCardView, because the Popover.prototype.applyFocus method is called, the user clicks inside the QuickCardView or the user uses the TAB | SHIFT + TAB key and the focus is goes inside the QuickCardView Closes: #1768 --- packages/main/src/Input.js | 12 +++- packages/main/src/InputPopover.hbs | 1 + packages/main/src/Popover.js | 15 ++--- packages/main/src/Popup.js | 22 ++++++- packages/main/src/TextArea.js | 2 +- packages/main/src/TextAreaPopover.hbs | 1 + .../src/popup-utils/OpenedPopupsRegistry.js | 4 ++ packages/main/src/popup-utils/PopupUtils.js | 29 +++++++++ packages/main/test/pages/Input.html | 24 +++---- packages/main/test/pages/Input_quickview.html | 62 ++++++++++++------- packages/main/test/specs/Input.spec.js | 19 +++++- 11 files changed, 137 insertions(+), 54 deletions(-) diff --git a/packages/main/src/Input.js b/packages/main/src/Input.js index bdd2a071f3d3..e956deaddf89 100644 --- a/packages/main/src/Input.js +++ b/packages/main/src/Input.js @@ -628,7 +628,7 @@ class Input extends UI5Element { } if (this.popover) { - this.popover.close(false, false, true); + this.popover.close(); } this.previousValue = ""; @@ -898,13 +898,19 @@ class Input extends UI5Element { onItemMouseOver(event) { const item = event.target; const suggestion = this.getSuggestionByListItem(item); - suggestion && suggestion.fireEvent("mouseover", { targetRef: item }); + suggestion && suggestion.fireEvent("mouseover", { + item: suggestion, + targetRef: item, + }); } onItemMouseOut(event) { const item = event.target; const suggestion = this.getSuggestionByListItem(item); - suggestion && suggestion.fireEvent("mouseout", { targetRef: item }); + suggestion && suggestion.fireEvent("mouseout", { + item: suggestion, + targetRef: item, + }); } onItemSelected(item, keyboardUsed) { diff --git a/packages/main/src/InputPopover.hbs b/packages/main/src/InputPopover.hbs index d845e73d4463..88e9bfb1f32e 100644 --- a/packages/main/src/InputPopover.hbs +++ b/packages/main/src/InputPopover.hbs @@ -65,6 +65,7 @@ { }; const _keydownListener = event => { + if (!openedRegistry.length) { + return; + } + if (isEscape(event)) { openedRegistry.pop().instance.close(true); } diff --git a/packages/main/src/popup-utils/PopupUtils.js b/packages/main/src/popup-utils/PopupUtils.js index 8b4a949c635c..c23c14b0906f 100644 --- a/packages/main/src/popup-utils/PopupUtils.js +++ b/packages/main/src/popup-utils/PopupUtils.js @@ -10,6 +10,34 @@ const getFocusedElement = () => { return (element && typeof element.focus === "function") ? element : null; }; +const isFocusedElementWithinNode = node => { + const fe = getFocusedElement(); + + if (fe) { + return isNodeContainedWithin(node, fe); + } + + return false; +}; + +const isNodeContainedWithin = (parent, child) => { + let currentNode = parent; + + if (currentNode.shadowRoot) { + currentNode = Array.from(currentNode.shadowRoot.children).find(n => n.localName !== "style"); + } + + if (currentNode === child) { + return true; + } + + const childNodes = currentNode.localName === "slot" ? currentNode.assignedNodes() : currentNode.children; + + if (childNodes) { + return Array.from(childNodes).some(n => isNodeContainedWithin(n, child)); + } +}; + const isPointInRect = (x, y, rect) => { return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; @@ -52,4 +80,5 @@ export { isClickInRect, getClosedPopupParent, getNextZIndex, + isFocusedElementWithinNode, }; diff --git a/packages/main/test/pages/Input.html b/packages/main/test/pages/Input.html index cd17ccefd689..0ee94e837f61 100644 --- a/packages/main/test/pages/Input.html +++ b/packages/main/test/pages/Input.html @@ -422,7 +422,7 @@

Test ariaLabel and ariaLabelledBy

inputPreview.addEventListener("ui5-suggestion-item-preview", function (event) { var item = event.detail.targetRef; quickViewCard.close(); - quickViewCard.openBy(item, true /* preventInitialFocus */, false /* closeWithOpener */); + quickViewCard.openBy(item, true /* preventInitialFocus */); // log info inputItemPreviewRes.value = item.textContent; @@ -449,7 +449,7 @@

Test ariaLabel and ariaLabelledBy

el.addEventListener("mouseover", function (event) { const targetRef = event.detail.targetRef; quickViewCard.close(); - quickViewCard.openBy(targetRef, true /* preventInitialFocus */, false /* closeWithOpener */); + quickViewCard.openBy(targetRef, true /* preventInitialFocus */); // log info mouseoverResult.value = targetRef.textContent; @@ -457,15 +457,6 @@

Test ariaLabel and ariaLabelledBy

}); el.addEventListener("mouseout", function (event) { - // if (!focusQuickView) { - // quickViewCard.close(false, false, true); - // } - - // focusQuickView = false; - - // // log info - // mouseoutResult.value = event.detail.targetRef.textContent; - // console.log("mouseout"); }); }); @@ -473,12 +464,13 @@

Test ariaLabel and ariaLabelledBy

console.log("scroll", { scrolltop: event.detail.scrollTop }); }); - inputPreview.addEventListener("focusin", function (event) { - console.log("focusin"); - }); + quickViewCard.addEventListener("ui5-before-close", async event => { + const esc = event.detail.escPressed; - inputPreview.addEventListener("focusout", function (event) { - console.log("focusout"); + if (esc) { + await RenderScheduler.whenFinished(); + inputPreview.focus(); + } }); scrollInput.addEventListener("ui5-suggestion-scroll", function (event) { diff --git a/packages/main/test/pages/Input_quickview.html b/packages/main/test/pages/Input_quickview.html index f0f7a383cfd5..486b931d1625 100644 --- a/packages/main/test/pages/Input_quickview.html +++ b/packages/main/test/pages/Input_quickview.html @@ -18,7 +18,14 @@

Quick View sample

  • navigate via the arrows to see quick view
  • press [ctrl + shift + 1] to enter the quick view
  • - + +
    + focusable element: before +
    +
    +
    + + @@ -26,7 +33,8 @@

    Quick View sample

    - + + @@ -42,23 +50,43 @@

    Quick View sample

    +
    +
    +
    + focusable element: after +
    diff --git a/packages/main/test/specs/Input.spec.js b/packages/main/test/specs/Input.spec.js index b33a932b5ed5..6583ab963d05 100644 --- a/packages/main/test/specs/Input.spec.js +++ b/packages/main/test/specs/Input.spec.js @@ -132,11 +132,28 @@ describe("Input general interaction", () => { it("fires suggestion-item-preview", () => { const inputItemPreview = $("#inputPreview").shadow$("input"); const inputItemPreviewRes = $("#inputItemPreviewRes"); + const EXPECTED_PREVIEW_ITEM_TEXT = "Laptop Lenovo"; + // act inputItemPreview.click(); inputItemPreview.keys("ArrowDown"); + + // assert + const staticAreaItemClassName = browser.getStaticAreaItemClassName("#inputPreview"); + const inputPopover = browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-responsive-popover"); + const helpPopover = browser.$("#quickViewCard"); + + assert.strictEqual(inputItemPreviewRes.getValue(), EXPECTED_PREVIEW_ITEM_TEXT, "First item has been previewed"); + assert.ok(helpPopover.isDisplayedInViewport(), "The help popover is open."); + assert.ok(inputPopover.isDisplayedInViewport(), "The input popover is open."); - assert.strictEqual(inputItemPreviewRes.getValue(), "Laptop Lenovo", "First item has been previewed"); + // act + const inputInHelpPopover = browser.$("#searchInput").shadow$("input"); + inputInHelpPopover.click(); + + // assert + assert.notOk(inputPopover.isDisplayedInViewport(), "The inpuit popover is closed as it lost the focus."); + assert.ok(helpPopover.isDisplayedInViewport(), "The help popover remains open as the focus is within."); }); it("fires suggestion-scroll event", () => {