Skip to content

Commit

Permalink
feat(ld-select): typeahead to focus an option
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdiakur committed Jul 15, 2021
1 parent c5921e6 commit 240fed8
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 39 deletions.
103 changes: 85 additions & 18 deletions src/liquid/components/ld-select/ld-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export class LdSelect {
/** Size of the select trigger button. */
@Prop() size?: 'sm' | 'lg'

// TODO: implement compact mode
// Constrain the display

/** Attached as CSS class to the select popper element. */
@Prop() popperClass: string

Expand All @@ -91,6 +94,10 @@ export class LdSelect {

@State() ariaDisabled = false

@State() typeAheadQuery: string

@State() typeAheadTimeout: number

@Watch('selected')
emitEvents(newSelection: LdOption[], oldSelection: LdOption[]) {
if (!this.initialized) return
Expand All @@ -103,6 +110,30 @@ export class LdSelect {
this.change.emit(newValues)
}

@Watch('typeAheadQuery')
handleTypeAhead(newQuery?: string) {
if (!newQuery) return

const options = Array.from(this.popperRef.querySelectorAll('ld-option'))
const values = options.map((option) => option.value)
let index = values.findIndex(
(value) => value.toLowerCase().indexOf(newQuery.toLowerCase()) === 0
)
if (index > -1) {
options[index].focus()
return
}

index = [newQuery, ...values]
.sort()
.findIndex(
(value) => value.toLowerCase().indexOf(newQuery.toLowerCase()) === 0
)
if (index > 0) {
options[index - 1].focus()
}
}

/**
* Emitted with an array of selected values when an alteration to the selection is committed by the user.
*/
Expand Down Expand Up @@ -315,6 +346,38 @@ export class LdSelect {
}
}

private selectAndFocus(ev, option) {
if (!option) return

if (this.multiple && ev.shiftKey) {
if (
document.activeElement?.classList.contains('ld-option') &&
document.activeElement.getAttribute('aria-selected') !== 'true'
) {
document.activeElement.dispatchEvent(
new KeyboardEvent('keydown', { key: ' ' })
)
}
option.focus()
if (option.getAttribute('aria-selected') !== 'true') {
option.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
}
} else {
option.focus()
}
}

private typeAhead(key: string) {
// Type a character: focus moves to the next item with a name that starts with the typed character.
// Type multiple characters in rapid succession: focus moves to the next item with a name that starts
// with the string of characters typed.
window.clearTimeout(this.typeAheadTimeout)
this.typeAheadQuery = (this.typeAheadQuery || '') + key
this.typeAheadTimeout = window.setTimeout(() => {
this.typeAheadQuery = ''
}, 500)
}

@Listen('keydown', { passive: false, target: 'window' })
handleKeyDown(ev: KeyboardEvent) {
if (this.disabled || this.ariaDisabled) return
Expand Down Expand Up @@ -347,25 +410,29 @@ export class LdSelect {
case 'ArrowDown': {
// If not expanded, expand popper.
// If expanded, move focus to the next option.
// If shift is pressed, select the next option.
// Holding down the Shift key and then using the Down cursor keys
// increases the range of items selected.
ev.preventDefault()
if (this.expanded) {
if (ev.metaKey) {
this.handleEnd(ev)
return
}

let nextOption
if (
document.activeElement.nextElementSibling &&
document.activeElement.nextElementSibling.classList.contains(
document.activeElement.nextElementSibling?.classList.contains(
'ld-option'
)
) {
;(document.activeElement.nextElementSibling as HTMLElement)?.focus()
nextOption = document.activeElement.nextElementSibling
} else {
if (document.activeElement === this.triggerRef) {
this.popperRef.querySelector('ld-option')?.focus()
nextOption = this.popperRef.querySelector('ld-option')
}
}
this.selectAndFocus(ev, nextOption)
} else {
this.expandAndFocus()
}
Expand All @@ -375,31 +442,33 @@ export class LdSelect {
// If not expanded, expand popper.
// If expanded, move focus to the previous option.
// If the first option is focused, focus the trigger button.
// Holding down the Shift key and then using the Up cursor keys
// increases the range of items selected.
ev.preventDefault()
if (this.expanded) {
if (ev.metaKey) {
this.handleHome(ev)
return
}

let prevOption
if (
document.activeElement.previousElementSibling &&
document.activeElement.previousElementSibling.classList.contains(
document.activeElement.previousElementSibling?.classList.contains(
'ld-option'
)
) {
;(document.activeElement
.previousElementSibling as HTMLElement)?.focus()
prevOption = document.activeElement.previousElementSibling
} else {
if (document.activeElement === this.triggerRef && !this.expanded) {
this.popperRef.querySelector('ld-option')?.focus()
prevOption = this.popperRef.querySelector('ld-option')
} else if (
document.activeElement ===
this.popperRef.querySelector('ld-option')
) {
this.triggerRef.focus()
}
}
this.selectAndFocus(ev, prevOption)
} else {
this.expandAndFocus()
}
Expand Down Expand Up @@ -457,16 +526,13 @@ export class LdSelect {
ev.stopImmediatePropagation()
}
break
default:
if (this.expanded) {
ev.preventDefault()
ev.stopImmediatePropagation()
this.typeAhead(ev.key)
}
}

// TODO: implement Shift+Up and Shift+Down selection for multiple mode
// Holding down the Shift key and then using the Up and Down cursor keys
// increases or decreases the range of items selected.

// TODO: implement type-ahead
// Type a character: focus moves to the next item with a name that starts with the typed character.
// Type multiple characters in rapid succession: focus moves to the next item with a name that starts
// with the string of characters typed.
}

@Listen('click', {
Expand Down Expand Up @@ -588,6 +654,7 @@ export class LdSelect {
}

disconnectedCallback() {
window.clearTimeout(this.typeAheadTimeout)
if (this.popper) this.popper.destroy()
if (this.observer) this.observer.disconnect()
}
Expand Down
42 changes: 21 additions & 21 deletions src/liquid/components/ld-select/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permalink: components/ld-select/
### Single select mode

{% example %}
<ld-select placeholder="Pick a fruit">
<ld-select placeholder="Pick a fruit" name="fruit">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand Down Expand Up @@ -75,7 +75,7 @@ permalink: components/ld-select/
### Multiple select mode

{% example %}
<ld-select placeholder="Pick some fruits" multiple>
<ld-select placeholder="Pick some fruits" name="fruits" multiple>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -97,7 +97,7 @@ permalink: components/ld-select/
### Disabled

{% example %}
<ld-select placeholder="Pick a fruit" disabled>
<ld-select placeholder="Pick a fruit" name="fruit" disabled>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -115,7 +115,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple disabled>
<ld-select placeholder="Pick some fruits" name="fruits" multiple disabled>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -133,7 +133,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick a fruit" disabled>
<ld-select placeholder="Pick a fruit" name="fruit" disabled>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana" selected>Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -151,7 +151,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple disabled>
<ld-select placeholder="Pick some fruits" name="fruits" multiple disabled>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana" selected>Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand Down Expand Up @@ -210,7 +210,7 @@ permalink: components/ld-select/
**If you want the select to stay focusable** even if it is disabled, use `aria-disabled` in place of `disabled`:

{% example %}
<ld-select placeholder="Pick a fruit" aria-disabled="true">
<ld-select placeholder="Pick a fruit" name="fruit" aria-disabled="true">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -228,7 +228,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple aria-disabled="true">
<ld-select placeholder="Pick some fruits" name="fruits" multiple aria-disabled="true">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -246,7 +246,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick a fruit" aria-disabled="true">
<ld-select placeholder="Pick a fruit" name="fruit" aria-disabled="true">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana" selected>Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -264,7 +264,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple aria-disabled="true">
<ld-select placeholder="Pick some fruits" name="fruits" multiple aria-disabled="true">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana" selected>Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand Down Expand Up @@ -325,7 +325,7 @@ permalink: components/ld-select/
### Invalid

{% example %}
<ld-select placeholder="Pick a fruit" invalid mode="detached">
<ld-select placeholder="Pick a fruit" name="fruit" invalid mode="detached">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -343,7 +343,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple invalid>
<ld-select placeholder="Pick some fruits" name="fruits" multiple invalid>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana" selected>Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand Down Expand Up @@ -402,7 +402,7 @@ permalink: components/ld-select/
### Detached

{% example %}
<ld-select placeholder="Pick a fruit" mode="detached">
<ld-select placeholder="Pick a fruit" name="fruit" mode="detached">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -424,7 +424,7 @@ permalink: components/ld-select/
### Inline

{% example %}
<ld-select placeholder="Pick a fruit" mode="inline">
<ld-select placeholder="Pick a fruit" name="fruit" mode="inline">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -446,7 +446,7 @@ permalink: components/ld-select/
### Ghost

{% example %}
<ld-select placeholder="Pick a fruit" mode="ghost">
<ld-select placeholder="Pick a fruit" name="fruit" mode="ghost">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -468,7 +468,7 @@ permalink: components/ld-select/
### Size

{% example %}
<ld-select placeholder="Pick a fruit" size="sm">
<ld-select placeholder="Pick a fruit" name="fruit" size="sm">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -486,7 +486,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick a fruit">
<ld-select placeholder="Pick a fruit" name="fruit">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -504,7 +504,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick a fruit" size="lg">
<ld-select placeholder="Pick a fruit" name="fruit" size="lg">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -522,7 +522,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple size="sm">
<ld-select placeholder="Pick some fruits" name="fruits" multiple size="sm">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -540,7 +540,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple>
<ld-select placeholder="Pick some fruits" name="fruits" multiple>
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand All @@ -558,7 +558,7 @@ permalink: components/ld-select/
<ld-option value="plum">Plum</ld-option>
</ld-select>

<ld-select placeholder="Pick some fruits" multiple size="lg">
<ld-select placeholder="Pick some fruits" name="fruits" multiple size="lg">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
Expand Down

0 comments on commit 240fed8

Please sign in to comment.