Skip to content

Commit

Permalink
feat(ld-menu): exclude current from typeahead
Browse files Browse the repository at this point in the history
  • Loading branch information
renet committed Mar 30, 2023
1 parent 936bf2b commit 1d12e21
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 13 deletions.
6 changes: 5 additions & 1 deletion src/liquid/components/ld-context-menu/ld-menu/ld-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class LdMenu {
this.focusLast(target)
break
default:
focusedElement = this.typeAheadHandler.typeAhead(event.key)
focusedElement = this.typeAheadHandler.typeAhead(event.key, target)

if (focusedElement) {
target.ldTabindex = -1
Expand All @@ -191,6 +191,10 @@ export class LdMenu {
this.typeAheadHandler = new TypeAheadHandler(this.getAllMenuItems())
}

disconnectedCallback() {
this.typeAheadHandler.clearTimeout()
}

render() {
return (
<Host onKeyDown={this.handleKeyDown}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ describe('ld-menu', () => {
expect(item2.focusInner).not.toHaveBeenCalled()
expect(item3.focusInner).toHaveBeenCalled()

jest.advanceTimersByTime(500)
item3.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'f',
Expand All @@ -109,6 +110,7 @@ describe('ld-menu', () => {

expect(item4.focusInner).toHaveBeenCalled()

jest.advanceTimersByTime(500)
item4.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'f',
Expand All @@ -118,6 +120,34 @@ describe('ld-menu', () => {
)

expect(item1.focusInner).toHaveBeenCalled()

jest.advanceTimersByTime(500)
item4.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'f',
bubbles: true,
composed: true,
})
)
item4.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'i',
bubbles: true,
composed: true,
})
)
item4.dispatchEvent(
new KeyboardEvent('keydown', {
key: 's',
bubbles: true,
composed: true,
})
)

expect(item1.focusInner).toHaveBeenCalledTimes(3)
expect(item2.focusInner).toHaveBeenCalledTimes(0)
expect(item3.focusInner).toHaveBeenCalledTimes(1)
expect(item4.focusInner).toHaveBeenCalledTimes(2)
})

it('focuses last item on End key', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const prepareAndGetMenuInTooltip = async (
menuInTooltip.querySelectorAll<HTMLLdMenuitemElement>('ld-menuitem')
)

console.log(menuItemsInTooltip.length)
const innerFocusableElements = [...menuItems, ...menuItemsInTooltip]

if (triggerLdButton) {
Expand Down
3 changes: 2 additions & 1 deletion src/liquid/components/ld-select/ld-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ export class LdSelect implements InnerFocusable {

this.listboxRef
.querySelector(`ld-option-internal[value='${optionValue}']`)
.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
?.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
}

componentWillLoad() {
Expand Down Expand Up @@ -1033,6 +1033,7 @@ export class LdSelect implements InnerFocusable {
this.popper?.destroy()
this.observer?.disconnect()
this.listboxRef?.remove()
this.typeAheadHandler?.clearTimeout()
}

render() {
Expand Down
29 changes: 19 additions & 10 deletions src/liquid/utils/keyboard-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const isPrintableCharacter = (key: string) =>
key.length === 1 && key.match(/\S/)

export class TypeAheadHandler<T extends HTMLElement> {
private currentIndex: number
private typeAheadQuery = ''
private typeAheadTimeout: NodeJS.Timeout
private _options: T[]
Expand All @@ -12,29 +13,25 @@ export class TypeAheadHandler<T extends HTMLElement> {
this.options = optionNodes
}

disconnectedCallback() {
clearTimeout(this.typeAheadTimeout)
}

set options(optionNodes: NodeListOf<T> | T[]) {
this._options = Array.isArray(optionNodes)
? optionNodes
: Array.from(optionNodes)
}

private getElementByQuery = (currentElement?: T) => {
private getElementByQuery = () => {
if (!this.typeAheadQuery) return

const currentIndex = this._options.indexOf(currentElement)
const query = this.typeAheadQuery.toLowerCase()
const values = this._options.map((option) =>
option.textContent.trim().toLowerCase()
)
let index = values.findIndex(
(value, index) => index > currentIndex && value.indexOf(query) === 0
(value, index) => index > this.currentIndex && value.indexOf(query) === 0
)

if (index === -1) {
// only search again from index 0, if you previously started from an index > 0
if (index === -1 && this.currentIndex > -1) {
index = values.findIndex((value) => value.indexOf(query) === 0)
}

Expand All @@ -50,6 +47,11 @@ export class TypeAheadHandler<T extends HTMLElement> {
}
}

/* Clear timeout */
clearTimeout() {
clearTimeout(this.typeAheadTimeout)
}

/**
* Type a character: focus moves to the next item with a name that starts
* with the typed character. Type multiple characters in rapid succession:
Expand All @@ -63,15 +65,22 @@ export class TypeAheadHandler<T extends HTMLElement> {
return
}

// set the current index only once while adding to a typeahead query
if (!this.typeAheadQuery) {
this.currentIndex = currentElement
? this._options.indexOf(currentElement)
: -1
}

this.typeAheadQuery = this.typeAheadQuery + key

const focusableElement = this.getElementByQuery(currentElement)
const focusableElement = this.getElementByQuery()

if (focusableElement) {
focusInnerOrFocus(focusableElement)
}

clearTimeout(this.typeAheadTimeout)
this.clearTimeout()
this.typeAheadTimeout = setTimeout(() => {
this.typeAheadQuery = ''
}, 500)
Expand Down

0 comments on commit 1d12e21

Please sign in to comment.