diff --git a/headless-demo/tests/components/combobox.spec.ts b/headless-demo/tests/components/combobox.spec.ts index 4849f1241..9a540a927 100644 --- a/headless-demo/tests/components/combobox.spec.ts +++ b/headless-demo/tests/components/combobox.spec.ts @@ -412,10 +412,11 @@ test("Keyboard selection in lazy mode does nothing when the dropdown is closed", await page.locator("#checkbox-enable-lazy-opening").check(); await input.click(); - await input.pressSequentially("oma"); + await expect(items).toBeVisible(); await page.mouse.click(0, 0); + await expect(items).toBeHidden(); await input.click(); await expect(input).toBeFocused(); 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"