Skip to content

Commit

Permalink
Added keyboard support to examples suggestions
Browse files Browse the repository at this point in the history
Signed-off-by: paulober <[email protected]>
  • Loading branch information
paulober committed Sep 6, 2024
1 parent aa79457 commit 48f777d
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 25 deletions.
31 changes: 15 additions & 16 deletions src/webview/newProjectPanel.mts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,26 +1495,25 @@ export class NewProjectPanel {
? "Select an example"
: "Project name"
}" required/> <!-- without this required the webview will crash every time you hit the examples button -->
<button id="project-name-dropdown-button" class="absolute inset-y-0 right-0 flex items-center px-2 border border-l-0 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white rounded-r-lg border-gray-300 dark:border-gray-600 ${
<button id="project-name-dropdown-button" class="absolute inset-y-0 right-0 flex items-center px-2 border bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white rounded-r-lg border-gray-300 dark:border-gray-600 ${
!forceCreateFromExample ? "hidden" : ""
}">&#9660;</button>
${
this._examples.length > 0
? `
<ul id="examples-list" class="bg-gray-50 border-gray-300 dark:bg-gray-700 dark:border-gray-600 rounded-b-lg"></ul>
<!--<datalist id="examples-list">
<option value="\${this._examples
.map(e => e.searchKey)
.join(
'">example project</option>\n<option value="'
)}">example project</option>
</datalist>-->`
: ""
}
</div>
<button id="btn-create-from-example" class="focus:outline-none bg-transparent ring-2 focus:ring-3 ring-blue-400 dark:ring-blue-700 font-medium rounded-lg px-4 ml-2 hover:bg-blue-500 dark:hover:bg-blue-700 focus:ring-blue-600 dark:focus:ring-blue-800" tooltip="Create from example">Example</button>
<button id="btn-create-from-example" class="focus:outline-none bg-transparent ring-2 focus:ring-3 ring-blue-400 dark:ring-blue-700 font-medium rounded-lg px-4 ml-4 hover:bg-blue-500 dark:hover:bg-blue-700 focus:ring-blue-600 dark:focus:ring-blue-800" tooltip="Create from example">Example</button>
</div>
${
this._examples.length > 0
? `
<ul id="examples-list"></ul>
<!--<datalist id="examples-list">
<option value="\${this._examples
.map(e => e.searchKey)
.join(
'">example project</option>\n<option value="'
)}">example project</option>
</datalist>-->`
: ""
}
<p id="inp-project-name-error" class="mt-2 text-sm text-red-600 dark:text-red-500" hidden>
<span class="font-medium">Error</span> Please enter a valid project name.
Expand Down
19 changes: 12 additions & 7 deletions web/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ ul#examples-list {
list-style: none;
width: 100%;
/* background-color: var(--vscode-editor-background); */
background-color: var(--vscode-input-background);
border-radius: 0 0 5px 5px;
margin-top: 5px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
/* background-color: var(--vscode-input-background); */
/* border-radius: 0 0 5px 5px; */
/* 5px if outside of the input div */
margin-top: 35px;
/* box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); */

overflow-y: auto;
/* window height dependend max-height */
Expand All @@ -116,7 +117,11 @@ ul#examples-list {
border-radius: 0 0 5px 5px;
}

.examples-list-suggestion:hover {
/* background-color: var(--vscode-inputOption-hoverBackground); */
background-color: var(--vscode-button-hoverBackground);
.examples-list-suggestion.hovered {
background-color: var(--vscode-inputOption-hoverBackground);
}

.examples-list-suggestion:hover:not(.unhovered) {
background-color: var(--vscode-inputOption-hoverBackground);
/* background-color: var(--vscode-button-hoverBackground); */
}
165 changes: 163 additions & 2 deletions web/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ window.toggleCreateFromExampleMode = function (forceOn, forceOff) {
projectNameDropdownButton.classList.add('hidden');
// crashes the webview
//projectNameInput.required = true;

// crashes the webview
/*if (window.removeExampleItems) {
window.removeExampleItems();
}*/
}

if (defaultBoardTypeOption) {
Expand Down Expand Up @@ -188,7 +193,18 @@ window.toggleCreateFromExampleMode = function (forceOn, forceOff) {
}

window.removeExampleItems = window.removeExampleItems || function () {
if (window.selectedSuggestion) {
window.selectedSuggestion.classList.remove('hovered');
window.selectedSuggestion.classList.remove('unhovered');
window.selectedSuggestion = null;
}
if (examplesList !== null) {
projectNameInput.removeEventListener('keydown', window.suggestionsOnKeydownNav);
projectNameDropdownButton.removeEventListener('keydown', window.suggestionsOnKeyupNav);
projectNameInput.classList.replace('rounded-t-lg', 'rounded-lg');
projectNameDropdownButton.classList.replace("rounded-tr-lg", "rounded-r-lg");
examplesList.classList.remove('border');

// clear ul
examplesList.innerHTML = '';
}
Expand All @@ -211,14 +227,109 @@ window.toggleCreateFromExampleMode = function (forceOn, forceOff) {

window.handleOutsideSuggestionsClick = window.handleOutsideSuggestionsClick || function (event) {
// check if the clicked element is not inside the examplesList
if (!examplesList.contains(event.target) && event.target !== projectNameDropdownButton) {
if (!examplesList.contains(event.target) && event.target !== projectNameDropdownButton && event.target !== projectNameInput) {
// click occurred outside the suggestions "popup" so remove the suggestions
removeExampleItems();
removeClickOutsideSuggestionsListener();
}
};

window.suggestionsOnKeydownNav = window.suggestionsOnKeydownNav || function (event, arg2) {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
if (arg2) {
return true;
}
if (examplesList.childNodes.length === 0) {
return;
}
const selected = window.selectedSuggestion;
const suggestions = document.querySelectorAll('.examples-list-suggestion');
if (suggestions.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
const index = selected ? Array.from(suggestions).indexOf(selected) : -1;

if (selected) {
selected.classList.remove('hovered');
selected.classList.add('unhovered');
}

if (event.key === 'ArrowDown') {
if (index === suggestions.length - 1) {
suggestions[0].classList.add('hovered');
suggestions[0].classList.remove('unhovered');
window.selectedSuggestion = suggestions[0];
} else {
suggestions[index + 1].classList.add('hovered');
suggestions[index + 1].classList.remove('unhovered');
window.selectedSuggestion = suggestions[index + 1];
}
} else {
if (index <= 0) {
suggestions[suggestions.length - 1].classList.add('hovered');
suggestions[suggestions.length - 1].classList.remove('unhovered');
window.selectedSuggestion = suggestions[suggestions.length - 1];
} else {
suggestions[index - 1].classList.add('hovered');
suggestions[index - 1].classList.remove('unhovered');
window.selectedSuggestion = suggestions[index - 1];
}
}
window.isElementInView = window.isElementInView || ((el, container) => {
const containerRect = container.getBoundingClientRect();
const elementRect = el.getBoundingClientRect();

// Check if the element is outside of the visible container bounds
const isAbove = elementRect.top < containerRect.top;
const isBelow = elementRect.bottom > containerRect.bottom;

return !isAbove && !isBelow;
});
// if not in view then set window.ignoreNextMouseOver to true
if (!isElementInView(window.selectedSuggestion, examplesList)) {
window.ignoreNextMouseOver = true;

// Scroll the selected suggestion into view
window.selectedSuggestion.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
}
} else if (event.key === 'Enter') {
if (arg2) {
return true;
}
if (examplesList.childNodes.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
if (window.selectedSuggestion) {
window.selectedSuggestion.click();
}
} else if (event.key === 'Escape') {
if (arg2) {
return true;
}
if (examplesList.childNodes.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
removeExampleItems();
}

if (arg2) {
return false;
}
};

window.projectNameInputOnKeyup = window.projectNameInputOnKeyup || function (e) {
// call to see if this is a key that is even relevant for the suggestions
if (window.suggestionsOnKeydownNav(e, true)) {
// return as true means the event was handled
return;
}

removeExampleItems();

if (!clickOutsideSuggestionsListenerAdded) {
Expand All @@ -232,6 +343,13 @@ window.toggleCreateFromExampleMode = function (forceOn, forceOff) {
for (let i of Object.keys(examples).sort()) {
// startsWith was to strict for the examples name format we use
if (isInputEmpty || i.toLowerCase().includes(projectNameInput.value.toLowerCase())) {
// TODO: check if not added multiple times
projectNameInput.addEventListener('keydown', suggestionsOnKeydownNav);
projectNameDropdownButton.addEventListener('keydown', suggestionsOnKeydownNav);
projectNameInput.classList.replace('rounded-lg', 'rounded-t-lg');
projectNameDropdownButton.classList.replace("rounded-r-lg", "rounded-tr-lg");
examplesList.classList.add('border');

// create li element
let listItem = document.createElement("li");
// one common class name
Expand All @@ -244,6 +362,48 @@ window.toggleCreateFromExampleMode = function (forceOn, forceOff) {
removeClickOutsideSuggestionsListener();
examplesListSelect(i);
});
listItem.addEventListener('mouseover', () => {
if (window.ignoreNextMouseOver) {
// check if element is now in view if not then don't set ignoreMouseOver to false
if (window.isElementInView(window.selectedSuggestion, examplesList)) {
window.ignoreNextMouseOver = false;
}

return;
}
if (window.selectedSuggestion) {
if (window.selectedSuggestion === listItem) {
// so it can automatically lose the hover effect
listItem.classList.remove('hovered');
listItem.classList.remove('unhovered');
return;
} else {
window.selectedSuggestion.classList.remove('hovered');
window.selectedSuggestion.classList.add('unhovered');
}
}
window.selectedSuggestion = listItem; // Set the hovered element as "selected"
listItem.classList.add('hovered');
listItem.classList.remove('unhovered');
});
listItem.addEventListener('mouseout', () => {
if (window.ignoreNextMouseOver) {
// check if element is now in view if not then don't set ignoreMouseOver to false
if (window.isElementInView(window.selectedSuggestion, examplesList)) {
window.ignoreNextMouseOver = false;
}

return;
}
listItem.classList.remove('hovered');
listItem.classList.add('unhovered');
if (window.selectedSuggestion === listItem) {
window.selectedSuggestion = undefined;
} else {
window.selectedSuggestion.classList.remove('hovered');
window.selectedSuggestion.classList.add('unhovered');
}
});

// display matched part as bold text
// this is for .startsWith selector above
Expand Down Expand Up @@ -325,7 +485,7 @@ window.onload = function () {
}

if (createFromExampleBtn) {
createFromExampleBtn.addEventListener('click', function () {
createFromExampleBtn.addEventListener('click', () => {
toggleCreateFromExampleMode();
});
}
Expand All @@ -335,6 +495,7 @@ window.onload = function () {
// display: none; example btn
createFromExampleBtn.classList.add('hidden');
}

toggleCreateFromExampleMode(true);
} else {
// hide if not force from example
Expand Down

0 comments on commit 48f777d

Please sign in to comment.