diff --git a/eslint.config.cjs b/eslint.config.cjs index ee24402591f9e..c25e303ba5dbf 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -47,6 +47,7 @@ module.exports = [ findFormParent: "readonly", fireEvent: "readonly", Form: "readonly", + FormChecker: "readonly", getElementOverflowParams: "readonly", hoverNotification: "readonly", iota: "writeable", diff --git a/war/src/main/js/components/autocomplete/index.js b/war/src/main/js/components/autocomplete/index.js index 3f5a6367a2854..afa709139a2b3 100644 --- a/war/src/main/js/components/autocomplete/index.js +++ b/war/src/main/js/components/autocomplete/index.js @@ -23,20 +23,30 @@ function init() { function convertSuggestionToItem(suggestion, e) { const delimiter = e.getAttribute("autoCompleteDelimChar"); + const confirm = () => { + e.value = delimiter + ? addValue(e.value, suggestion.name, delimiter) + : suggestion.name; + validate(e); + e.focus(); + }; return { label: suggestion.name, - onClick: () => { - e.value = delimiter - ? addValue(e.value, suggestion.name, delimiter) - : suggestion.name; - validate(e); - e.focus(); + onClick: confirm, + onKeyPress: (evt) => { + if (evt.key == "Tab") { + confirm(); + e.dropdown.hide(); + evt.preventDefault(); + } }, }; } function createAndShowDropdown(e, div, suggestions) { - const items = suggestions.map((s) => convertSuggestionToItem(s, e)); + const items = suggestions + .splice(0, 10) + .map((s) => convertSuggestionToItem(s, e)); if (!e.dropdown) { Utils.generateDropdown( div, @@ -63,8 +73,23 @@ function init() { const url = e.getAttribute("autoCompleteUrl") + "?value=" + encodeURIComponent(word); fetch(url) - .then((rsp) => (rsp.ok ? rsp.json().suggestions : {})) - .then((response) => createAndShowDropdown(e, div, response)); + .then((rsp) => (rsp.ok ? rsp.json() : {})) + .then((response) => + createAndShowDropdown(e, div, response.suggestions || []), + ); + } + + function debounce(callback) { + callback.running = false; + return () => { + if (!callback.running) { + callback.running = true; + setTimeout(() => { + callback(); + callback.running = false; + }, 300); + } + }; } behaviorShim.specify( @@ -77,10 +102,12 @@ function init() { var div = document.createElement("DIV"); e.parentNode.insertBefore(div, e.nextElementSibling); e.style.position = "relative"; - - e.addEventListener("input", (evt) => { - updateSuggestions(e, div); - }); + e.addEventListener( + "input", + debounce(() => { + updateSuggestions(e, div); + }), + ); }, ); } diff --git a/war/src/main/js/components/dropdowns/templates.js b/war/src/main/js/components/dropdowns/templates.js index 713bf8b6ce0f7..eeef012ccf578 100644 --- a/war/src/main/js/components/dropdowns/templates.js +++ b/war/src/main/js/components/dropdowns/templates.js @@ -74,7 +74,9 @@ function menuItem(options) { if (options.onClick) { item.addEventListener("click", (event) => options.onClick(event)); } - + if (options.onKeyPress) { + item.onkeypress = options.onKeyPress; + } return item; } diff --git a/war/src/main/js/components/dropdowns/utils.js b/war/src/main/js/components/dropdowns/utils.js index 5ef11b640c801..be44d3736843e 100644 --- a/war/src/main/js/components/dropdowns/utils.js +++ b/war/src/main/js/components/dropdowns/utils.js @@ -91,7 +91,7 @@ function generateDropdownItems(items, compact) { menuItems, () => menuItems.querySelectorAll(".jenkins-dropdown__item"), SELECTED_ITEM_CLASS, - (selectedItem, key) => { + (selectedItem, key, evt) => { switch (key) { case "ArrowLeft": { const root = selectedItem.closest("[data-tippy-root]"); @@ -115,6 +115,10 @@ function generateDropdownItems(items, compact) { .classList.add(SELECTED_ITEM_CLASS); break; } + default: + if (selectedItem.onkeypress) { + selectedItem.onkeypress(evt); + } } }, (container) => { diff --git a/war/src/main/js/util/keyboard.js b/war/src/main/js/util/keyboard.js index 0196d8992f830..88d677530416a 100644 --- a/war/src/main/js/util/keyboard.js +++ b/war/src/main/js/util/keyboard.js @@ -74,7 +74,7 @@ export default function makeKeyboardNavigable( selectedItem.click(); } } else { - additionalBehaviours(selectedItem, e.key); + additionalBehaviours(selectedItem, e.key, e); } } });