From ee40fdaf70d532147adda033e1a3e2b37aee5804 Mon Sep 17 00:00:00 2001 From: Hauke Sommerfeld Date: Fri, 18 Oct 2024 12:43:44 +0200 Subject: [PATCH] Fixes the combobox deleting input when the rendering is slow This commit fixes a bug in the headless combobox component where parts of the input are deleted/reset when the user types fast and the rendering is slow. --- .../fritz2/headless/components/combobox.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/combobox.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/combobox.kt index ca9dbbd70..a20750f10 100644 --- a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/combobox.kt +++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/combobox.kt @@ -498,7 +498,6 @@ class Combobox(tag: Tag, id: String?) : Tag by tag, Op selectionStrategy.buildResult(query, itemSequence) } - @OptIn(FlowPreview::class) val queryResults: Flow> = merge( // Emit initial data straight-away to avoid flickering upon first opening of the dropdown: @@ -510,9 +509,7 @@ class Combobox(tag: Tag, id: String?) : Tag by tag, Op .drop(1) .map { it.items to it.query } .distinctUntilChanged() - .debounce(inputDebounceMillis) .mapLatest { (items, query) -> computeQueryResult(items, query) } - .debounce(renderDebounceMillis) ) init { @@ -619,21 +616,24 @@ class Combobox(tag: Tag, id: String?) : Tag by tag, Op private fun format(value: T?): String = value?.let(itemFormat) ?: "" + @OptIn(FlowPreview::class) fun render() { value( merge( - internalState.select.map { format(it) }, + internalState.select.map { format(it) }, value.data.flatMapLatest { value -> - internalState.resetQuery.map { format(value) } + internalState.resetQuery.transform { + // Before the input's value can be reset to the previous one we need to set it to + // the current typed value. This is needed because the underlying `mountSimple` function + // cannot handle repeating values. + emit(domNode.value) + emit(format(value)) + } }, - // Update the input every time the user types in a new value. This is needed because `mountSimple` - // (used internally by `value()`) does not work with repeating identical values. This is needed, - // however, when the previous selection should be re-applied to the input. - inputs.values() ).distinctUntilChanged() ) - inputs.values() handledBy internalState.updateQuery + inputs.values().debounce(inputDebounceMillis) handledBy internalState.updateQuery focuss.filterNot { domNode.readOnly } handledBy { @@ -740,8 +740,9 @@ class Combobox(tag: Tag, id: String?) : Tag by tag, Op * * @see comboboxItem */ + @OptIn(FlowPreview::class) val results: Flow> = - internalState.queryResults.mapNotNull { it as? ItemList } + internalState.queryResults.mapNotNull { it as? ItemList }.debounce(renderDebounceMillis) private fun itemId(index: Int) = "$componentId-item-$index"