Skip to content

Commit

Permalink
Only consider unselected portion when autocompleting
Browse files Browse the repository at this point in the history
Closes #40
  • Loading branch information
josefarias committed Mar 1, 2024
1 parent a1eda0a commit 8700722
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 9 deletions.
8 changes: 6 additions & 2 deletions app/assets/javascripts/controllers/hw_combobox_controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Combobox from "hw_combobox/models/combobox"
import { Concerns } from "hw_combobox/helpers"
import { Concerns, sleep } from "hw_combobox/helpers"
import { Controller } from "@hotwired/stimulus"

window.HotwireComboboxStreamDelay = 0 // ms, for testing purposes

const concerns = [
Controller,
Combobox.Actors,
Expand Down Expand Up @@ -70,10 +72,12 @@ export default class HwComboboxController extends Concerns(...concerns) {
}
}

endOfOptionsStreamTargetConnected(element) {
async endOfOptionsStreamTargetConnected(element) {
const inputType = element.dataset.inputType
const delay = window.HotwireComboboxStreamDelay

if (inputType && inputType !== "hw:ensureSelection") {
if (delay) await sleep(delay)
this._commitFilter({ inputType })
} else {
this._preselectOption()
Expand Down
14 changes: 13 additions & 1 deletion app/assets/javascripts/hw_combobox/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function startsWith(string, substring) {
return string.toLowerCase().startsWith(substring.toLowerCase())
}

export function debounce(fn, delay = 300) {
export function debounce(fn, delay = 150) {
let timeoutId = null

return (...args) => {
Expand All @@ -50,3 +50,15 @@ export function debounce(fn, delay = 300) {
export function isDeleteEvent(event) {
return event.inputType === "deleteContentBackward" || event.inputType === "deleteWordBackward"
}

export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

export function unselectedPortion(element) {
if (element.selectionStart === element.selectionEnd) {
return element.value
} else {
return element.value.substring(0, element.selectionStart)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Combobox from "hw_combobox/models/combobox/base"
import { startsWith } from "hw_combobox/helpers"
import { startsWith, unselectedPortion } from "hw_combobox/helpers"

Combobox.Autocomplete = Base => class extends Base {
_connectListAutocomplete() {
Expand All @@ -11,7 +11,7 @@ Combobox.Autocomplete = Base => class extends Base {
_autocompleteWith(option, { force }) {
if (!this._autocompletesInline && !force) return

const typedValue = this._query
const typedValue = unselectedPortion(this._actingCombobox)
const autocompletedValue = option.getAttribute(this.autocompletableAttributeValue)

if (force) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ Combobox.Selection = Base => class extends Base {
}
}

_select(option, { force = false } = {}) {
_select(option, { forceAutocomplete = false } = {}) {
this._resetOptions()

if (option) {
this._markValid()
this._autocompleteWith(option, { force })
this._autocompleteWith(option, { force: forceAutocomplete })
this._commitSelection(option, { selected: true })
} else {
this._markInvalid()
Expand Down Expand Up @@ -57,7 +57,7 @@ Combobox.Selection = Base => class extends Base {

_selectIndex(index) {
const option = wrapAroundAccess(this._visibleOptionElements, index)
this._select(option, { force: true })
this._select(option, { forceAutocomplete: true })
}

_preselectOption() {
Expand All @@ -72,7 +72,7 @@ Combobox.Selection = Base => class extends Base {

_ensureSelection() {
if (this._shouldEnsureSelection) {
this._select(this._ensurableOption, { force: true })
this._select(this._ensurableOption, { forceAutocomplete: true })
this.filter({ inputType: "hw:ensureSelection" })
}
}
Expand Down
27 changes: 27 additions & 0 deletions test/system/hotwire_combobox_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,20 @@ class HotwireComboboxTest < ApplicationSystemTestCase
click_away
end

test "async autocomplete selections don't trample over each other" do
visit async_path

on_slow_device delay: 0.5 do
open_combobox "#movie-field"
type_in_combobox "#movie-field", "a"
sleep 0.3 # less than the delay, more than the debounce
type_in_combobox "#movie-field", "l"
sleep 0.7 # more than the delay

assert_equal "addin", current_selection_contents
end
end

private
def open_combobox(selector)
find(selector).click
Expand Down Expand Up @@ -607,6 +621,15 @@ def on_small_screen
page.current_window.resize_to *original_size
end

def on_slow_device(delay:)
@on_slow_device = true
page.execute_script "window.HotwireComboboxStreamDelay = #{delay * 1000}"
yield
ensure
@on_slow_device = false
page.execute_script "window.HotwireComboboxStreamDelay = 0"
end

def tab_away
find("body").send_keys(:tab)
end
Expand All @@ -622,4 +645,8 @@ def click_away
def click_on_top_left_corner
page.execute_script "document.elementFromPoint(0, 0).click()"
end

def current_selection_contents
page.evaluate_script "document.getSelection().toString()"
end
end

0 comments on commit 8700722

Please sign in to comment.