Skip to content

Commit

Permalink
feat(select): improve option navigation by key (#856)
Browse files Browse the repository at this point in the history
This PR allows users to easily access target option based on their
keyboard input.

Closes [#686](#686)
  • Loading branch information
gokcecicek authored May 9, 2024
1 parent c6174cc commit a526c35
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/components/select/bl-select.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ ${SelectTemplate(args)}
# Select

<bl-badge icon="document">[ADR](https://github.com/Trendyol/baklava/issues/88)</bl-badge>
<bl-badge icon="document">[Accessibility](https://github.com/Trendyol/baklava/issues/686#issuecomment-2079522703)</bl-badge>
<bl-badge icon="puzzle">[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=25%3A3606)</bl-badge>

Select component is a component for selecting a value from a list of options.
Expand Down
101 changes: 101 additions & 0 deletions src/components/select/bl-select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,8 @@ describe("bl-select", () => {
<bl-select-option value="basketball">Basketball</bl-select-option>
<bl-select-option value="football">Football</bl-select-option>
<bl-select-option value="tennis">Tennis</bl-select-option>
<bl-select-option value="boxing">Boxing</bl-select-option>
<bl-select-option value="hockey" disabled>Hockey</bl-select-option>
</bl-select>
<input id="nextinput" />
</div>`);
Expand Down Expand Up @@ -758,6 +760,105 @@ describe("bl-select", () => {
//then
expect((document.activeElement as BlSelectOption).value).to.equal(firstOption?.value);
});

it("should focus the first matching option when typing a single character", async () => {
const firstOption = el.querySelector<BlSelectOption>("bl-select-option");

//given
await sendKeys({
press: tabKey,
});
await sendKeys({
press: "Space",
});
await sendKeys({
press: "b",
});

//then
expect((document.activeElement as BlSelectOption).value).to.equal(firstOption?.value);
});

it("should focus the first matching option when typing a single character with uppercase", async () => {
const firstOption = el.querySelector<BlSelectOption>("bl-select-option");

//given
await sendKeys({
press: tabKey,
});
await sendKeys({
press: "Space",
});
await sendKeys({
press: "B",
});

//then
expect((document.activeElement as BlSelectOption).value).to.equal(firstOption?.value);
});

it("should focus the first matching option when typing two characters", async () => {
const fourthOption = el.querySelector<BlSelectOption>("bl-select-option:nth-child(4)");

//given
await sendKeys({
press: tabKey,
});
await sendKeys({
press: "Space",
});
await sendKeys({
press: "b",
});
await sendKeys({
press: "o",
});

//then
expect((document.activeElement as BlSelectOption).value).to.equal(fourthOption?.value);
});

it("should reset typed characters after an interval of inactivity", async () => {
const secondOption = el.querySelector<BlSelectOption>("bl-select-option:nth-child(2)");

// when
await sendKeys({
press: tabKey,
});
await sendKeys({
press: "Space",
});
await sendKeys({
press: "b",
});
// Wait for an interval of inactivity
await new Promise(resolve => setTimeout(resolve, 600));

await sendKeys({
press: "f",
});

//then
expect((document.activeElement as BlSelectOption).value).to.equal(secondOption?.value);
});

it("should not focus on the disabled option even if it matches the typed character", async () => {
const focusedOptions = el.querySelectorAll("bl-select-option:focus");

//given
await sendKeys({
press: tabKey,
});
await sendKeys({
press: "Space",
});
await sendKeys({
press: "h",
});

//then
expect(focusedOptions.length).to.equal(0);
});
});

describe("select all", () => {
Expand Down
31 changes: 31 additions & 0 deletions src/components/select/bl-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,35 @@ export default class BlSelect<ValueType extends FormValue = string> extends Form
}

private focusedOptionIndex = -1;
private lastKeyPressedTime = 0;
private typedCharacters = "";
private keyPressThreshold = 500;

private handleFocusOptionByKey(key: string) {
const currentTime = Date.now();
const elapsedTimeSinceLastKeyPress = currentTime - this.lastKeyPressedTime;

if (elapsedTimeSinceLastKeyPress > this.keyPressThreshold) {
this.typedCharacters = "";
}

this.lastKeyPressedTime = currentTime;
this.typedCharacters += key.toLowerCase();

const matchingOptionIndex = this.options.findIndex(option => {
if (option.disabled) {
return false;
}
const optionText = option.innerText.trim().toLowerCase();

return optionText.startsWith(this.typedCharacters);
});

if (matchingOptionIndex !== -1) {
this.focusedOptionIndex = matchingOptionIndex;
this.options[matchingOptionIndex].focus();
}
}

private handleKeydown(event: KeyboardEvent) {
if (this.focusedOptionIndex === -1 && ["Enter", "Space"].includes(event.code)) {
Expand All @@ -595,6 +624,8 @@ export default class BlSelect<ValueType extends FormValue = string> extends Form
this.options[this.focusedOptionIndex].focus();

event.preventDefault();
} else if (this._isPopoverOpen && !this.searchBar) {
this.handleFocusOptionByKey(event.key);
}
}

Expand Down

0 comments on commit a526c35

Please sign in to comment.