From 81371b47972762b1b801d81bed5cd0bddcfca803 Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 13:44:23 +0100 Subject: [PATCH 1/6] Add blur event handling to input_chips to commit value on focus loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds a blur event handler to the input_chips component that automatically commits the current input value as a new chip when the input field loses focus. This provides a better user experience by allowing users to add chips without explicitly pressing Enter. The implementation: - Uses the existing addValue() method to respect the new-value-mode setting - Only adds non-empty values (trimmed) - Clears the input field after adding the value 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- nicegui/elements/input_chips.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nicegui/elements/input_chips.py b/nicegui/elements/input_chips.py index 1f629f4467..7733690104 100644 --- a/nicegui/elements/input_chips.py +++ b/nicegui/elements/input_chips.py @@ -21,6 +21,7 @@ def __init__(self, An input field that manages a collection of values as visual "chips" or tags. Users can type to add new chips and remove existing ones by clicking or using keyboard shortcuts. + Values are added by pressing Enter or when the input field loses focus (blur event). This element is based on Quasar's `QSelect `_ component. Unlike a traditional dropdown selection, this variant focuses on free-form text input with chips, @@ -52,5 +53,18 @@ def __init__(self, self._props['hide-dropdown-icon'] = True self._props['clearable'] = clearable + # Add blur event handler to commit input value when field loses focus + # This respects the new-value-mode setting by using the existing addValue method + self._props['@blur'] = ''' + function(event) { + const inputEl = event.target; + const val = inputEl?.value?.trim(); + if (val && this.addValue) { + this.addValue(val); + inputEl.value = ''; + } + } + ''' + def _event_args_to_value(self, e: GenericEventArguments) -> Any: return e.args or [] From 365875342c56c2ad4d202ae1c77faef16871adc8 Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 14:31:25 +0100 Subject: [PATCH 2/6] Implement blur event handling for input_chips to commit value on focus loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds blur event handling to the input_chips component using Python event listeners instead of JavaScript. When the input field loses focus, any typed value is automatically added as a chip. Implementation details: - Added _handle_input_value_change() to track what user is typing - Added _handle_blur() to process the value when field loses focus - Respects new-value-mode setting (add, add-unique, toggle) - Added comprehensive tests for blur functionality with all modes The solution works by: 1. Listening to 'input-value' events to track current input 2. Listening to 'blur' events to trigger chip creation 3. Applying the appropriate new-value-mode logic (add/add-unique/toggle) 4. Updating the component value which triggers UI update Tests: - All existing tests pass - New test_input_chips_blur_adds_value() validates blur behavior for all modes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- nicegui/elements/input_chips.py | 57 ++++++++++++++++++++++++++------- tests/test_input_chips.py | 38 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/nicegui/elements/input_chips.py b/nicegui/elements/input_chips.py index 7733690104..f321abdb50 100644 --- a/nicegui/elements/input_chips.py +++ b/nicegui/elements/input_chips.py @@ -53,18 +53,51 @@ def __init__(self, self._props['hide-dropdown-icon'] = True self._props['clearable'] = clearable - # Add blur event handler to commit input value when field loses focus - # This respects the new-value-mode setting by using the existing addValue method - self._props['@blur'] = ''' - function(event) { - const inputEl = event.target; - const val = inputEl?.value?.trim(); - if (val && this.addValue) { - this.addValue(val); - inputEl.value = ''; - } - } - ''' + # Track the current input value to add on blur + self._current_input_value = '' + self._new_value_mode = new_value_mode + + # Listen to input-value changes to track what user is typing + self.on('input-value', self._handle_input_value_change) + + # Listen to blur event to add the current input value as a chip + self.on('blur', self._handle_blur) + + def _handle_input_value_change(self, e: GenericEventArguments) -> None: + """Track the current input value as user types.""" + self._current_input_value = e.args if e.args else '' + + def _handle_blur(self, e: GenericEventArguments) -> None: + """Add the current input value as a chip when field loses focus.""" + val = self._current_input_value.strip() if isinstance(self._current_input_value, str) else '' + + if not val: + return + + # Get current chips + current_value = self.value if self.value else [] + + # Apply new-value-mode logic + if self._new_value_mode == 'add': + # Always add the value + new_value = current_value + [val] + elif self._new_value_mode == 'add-unique': + # Only add if not already present + if val not in current_value: + new_value = current_value + [val] + else: + return + elif self._new_value_mode == 'toggle': + # Toggle: add if not present, remove if present + if val in current_value: + new_value = [v for v in current_value if v != val] + else: + new_value = current_value + [val] + else: + return + + # Update the value + self.value = new_value def _event_args_to_value(self, e: GenericEventArguments) -> Any: return e.args or [] diff --git a/tests/test_input_chips.py b/tests/test_input_chips.py index 7da57c7415..820b288a19 100644 --- a/tests/test_input_chips.py +++ b/tests/test_input_chips.py @@ -46,3 +46,41 @@ def page(): screen.should_contain('Too many') else: screen.should_not_contain('Too many') + + +@pytest.mark.parametrize('new_value_mode', ['add', 'add-unique', 'toggle']) +def test_input_chips_blur_adds_value(screen: Screen, new_value_mode: str): + @ui.page('/') + def page(): + chips = ui.input_chips(new_value_mode=new_value_mode) + ui.label().bind_text_from(chips, 'value', lambda v: f'value = {v}') + + screen.open('/') + screen.should_contain('value = []') + + # Type a value and trigger blur by clicking elsewhere + input_field = screen.find_by_tag('input') + input_field.send_keys('chip1') + screen.wait(0.5) + + # Trigger blur by clicking on the label + screen.click('value = []') + screen.wait(0.5) + + # Should have added chip1 + screen.should_contain("value = ['chip1']") + + # Type another value and blur again + input_field = screen.find_by_tag('input') + input_field.send_keys('chip2') + screen.wait(0.5) + screen.click('value = ') + screen.wait(0.5) + + # Check based on new_value_mode + if new_value_mode == 'add': + screen.should_contain("value = ['chip1', 'chip2']") + elif new_value_mode == 'add-unique': + screen.should_contain("value = ['chip1', 'chip2']") + elif new_value_mode == 'toggle': + screen.should_contain("value = ['chip1', 'chip2']") From ef59a9c0ee1f76d6fea77060c8b242add07e9d46 Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 14:50:33 +0100 Subject: [PATCH 3/6] Fix blur test to properly validate new-value-mode behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous test didn't properly test the different new-value-mode behaviors. Now it mirrors the test_add_new_values test by: - Adding 'x' once - Adding 'y' twice - Validating that toggle mode removes the second 'y' (leaving only 'x') - Validating that add-unique mode keeps only one 'y' - Validating that add mode keeps both 'y' values This ensures blur behavior matches Enter key behavior for all modes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- tests/test_input_chips.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/test_input_chips.py b/tests/test_input_chips.py index 820b288a19..e6a6df7082 100644 --- a/tests/test_input_chips.py +++ b/tests/test_input_chips.py @@ -58,29 +58,23 @@ def page(): screen.open('/') screen.should_contain('value = []') - # Type a value and trigger blur by clicking elsewhere - input_field = screen.find_by_tag('input') - input_field.send_keys('chip1') + # Type 'x' and trigger blur + screen.find_by_tag('input').send_keys('x') screen.wait(0.5) - - # Trigger blur by clicking on the label screen.click('value = []') screen.wait(0.5) - # Should have added chip1 - screen.should_contain("value = ['chip1']") - - # Type another value and blur again - input_field = screen.find_by_tag('input') - input_field.send_keys('chip2') - screen.wait(0.5) - screen.click('value = ') - screen.wait(0.5) + # Type 'y' twice with blur each time (same as Enter test) + for _ in range(2): + screen.find_by_tag('input').send_keys('y') + screen.wait(0.5) + screen.click('value = ') # Trigger blur + screen.wait(0.5) - # Check based on new_value_mode + # Check based on new_value_mode (should match Enter behavior) if new_value_mode == 'add': - screen.should_contain("value = ['chip1', 'chip2']") + screen.should_contain("value = ['x', 'y', 'y']") elif new_value_mode == 'add-unique': - screen.should_contain("value = ['chip1', 'chip2']") + screen.should_contain("value = ['x', 'y']") elif new_value_mode == 'toggle': - screen.should_contain("value = ['chip1', 'chip2']") + screen.should_contain("value = ['x']") From 6e29fbb8d068e3e96060e4e1e7431a863bfd174e Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 15:02:45 +0100 Subject: [PATCH 4/6] Improve documentation and code style for blur functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation improvements: - Clarified docstring to explain blur behavior (Tab, click away) - Added explicit new_value_mode behavior descriptions with examples - Explained chip removal via "x" icon Code improvements: - Added explanatory comments in test showing expected behavior for each mode - Fixed linting warnings (use unpacking instead of concatenation) All tests pass, linting clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- nicegui/elements/input_chips.py | 16 +++++++++++----- tests/test_input_chips.py | 7 +++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/nicegui/elements/input_chips.py b/nicegui/elements/input_chips.py index f321abdb50..946641a137 100644 --- a/nicegui/elements/input_chips.py +++ b/nicegui/elements/input_chips.py @@ -20,8 +20,14 @@ def __init__(self, """Input Chips An input field that manages a collection of values as visual "chips" or tags. - Users can type to add new chips and remove existing ones by clicking or using keyboard shortcuts. - Values are added by pressing Enter or when the input field loses focus (blur event). + Users can add new chips by pressing Enter or when the input field loses focus (Tab, click away). + Chips can be removed by clicking the "x" icon on each chip. + + The ``new_value_mode`` parameter controls how duplicate values are handled: + + - ``'add'``: Always adds the value (allows duplicates) + - ``'add-unique'``: Only adds if not already present + - ``'toggle'``: Adds if absent, removes if present This element is based on Quasar's `QSelect `_ component. Unlike a traditional dropdown selection, this variant focuses on free-form text input with chips, @@ -80,11 +86,11 @@ def _handle_blur(self, e: GenericEventArguments) -> None: # Apply new-value-mode logic if self._new_value_mode == 'add': # Always add the value - new_value = current_value + [val] + new_value = [*current_value, val] elif self._new_value_mode == 'add-unique': # Only add if not already present if val not in current_value: - new_value = current_value + [val] + new_value = [*current_value, val] else: return elif self._new_value_mode == 'toggle': @@ -92,7 +98,7 @@ def _handle_blur(self, e: GenericEventArguments) -> None: if val in current_value: new_value = [v for v in current_value if v != val] else: - new_value = current_value + [val] + new_value = [*current_value, val] else: return diff --git a/tests/test_input_chips.py b/tests/test_input_chips.py index e6a6df7082..cbf7f37e11 100644 --- a/tests/test_input_chips.py +++ b/tests/test_input_chips.py @@ -64,14 +64,17 @@ def page(): screen.click('value = []') screen.wait(0.5) - # Type 'y' twice with blur each time (same as Enter test) + # Type 'y' twice with blur each time to test new_value_mode behavior: + # - 'add' keeps both: ['x', 'y', 'y'] + # - 'add-unique' deduplicates: ['x', 'y'] + # - 'toggle' removes second 'y': ['x'] for _ in range(2): screen.find_by_tag('input').send_keys('y') screen.wait(0.5) screen.click('value = ') # Trigger blur screen.wait(0.5) - # Check based on new_value_mode (should match Enter behavior) + # Verify behavior matches new_value_mode setting if new_value_mode == 'add': screen.should_contain("value = ['x', 'y', 'y']") elif new_value_mode == 'add-unique': From 3d6478c46e1ee1ead3dccba90abbe9c08652955b Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 15:45:59 +0100 Subject: [PATCH 5/6] Fix pylint warning: remove unused event parameter from _handle_blur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _handle_blur method doesn't use the event parameter - it only uses the tracked _current_input_value. Removing the parameter follows the NiceGUI pattern where handlers without event data don't take parameters. Pylint now rates the file 10.00/10. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- nicegui/elements/input_chips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nicegui/elements/input_chips.py b/nicegui/elements/input_chips.py index 946641a137..0bc58c8906 100644 --- a/nicegui/elements/input_chips.py +++ b/nicegui/elements/input_chips.py @@ -73,7 +73,7 @@ def _handle_input_value_change(self, e: GenericEventArguments) -> None: """Track the current input value as user types.""" self._current_input_value = e.args if e.args else '' - def _handle_blur(self, e: GenericEventArguments) -> None: + def _handle_blur(self) -> None: """Add the current input value as a chip when field loses focus.""" val = self._current_input_value.strip() if isinstance(self._current_input_value, str) else '' From 00f6ac381f46c31e5c8ae103f04c1e57508abd62 Mon Sep 17 00:00:00 2001 From: kri-bak Date: Wed, 28 Jan 2026 16:21:37 +0100 Subject: [PATCH 6/6] Add example demonstrating input_chips blur functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a comprehensive example showing how input_chips automatically adds values when the field loses focus (Tab, click away) in addition to Enter key. The example demonstrates all three new_value_mode options: - toggle: Adds if absent, removes if present - add: Always adds (allows duplicates) - add-unique: Only adds if not already present This provides users with a clear reference for the blur feature. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Kristian Bak --- examples/chips_on_blur/README.md | 8 ++++++ examples/chips_on_blur/main.py | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 examples/chips_on_blur/README.md create mode 100644 examples/chips_on_blur/main.py diff --git a/examples/chips_on_blur/README.md b/examples/chips_on_blur/README.md new file mode 100644 index 0000000000..638db5f93e --- /dev/null +++ b/examples/chips_on_blur/README.md @@ -0,0 +1,8 @@ +# Input Chips with Blur Support + +Demonstrates how `ui.input_chips` automatically adds typed values as chips when the input field loses focus (blur event), in addition to the Enter key behavior. + +The example shows all three `new_value_mode` options: +- **toggle**: Adds value if absent, removes if present +- **add**: Always adds values (allows duplicates) +- **add-unique**: Only adds if not already present diff --git a/examples/chips_on_blur/main.py b/examples/chips_on_blur/main.py new file mode 100644 index 0000000000..96d6220bf6 --- /dev/null +++ b/examples/chips_on_blur/main.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Demonstrates input_chips blur functionality. + +This example shows how input_chips automatically adds typed values as chips +when the field loses focus (blur event), in addition to the default Enter key behavior. +""" +from nicegui import ui + +ui.markdown('## Input Chips with Blur Support') +ui.markdown(''' +Type a value and press **Tab** or **click elsewhere** to add it as a chip. +The **Enter** key also works (original behavior). +''') + +ui.separator() + +# Toggle mode (default) +with ui.card().classes('w-full'): + ui.label('Toggle Mode (default)').classes('text-h6') + ui.markdown('Adding the same value twice **removes** it.') + chips_toggle = ui.input_chips(label='Try typing "test" twice', new_value_mode='toggle') + ui.label().bind_text_from(chips_toggle, 'value', lambda v: f'Chips: {v}') + +ui.separator() + +# Add mode +with ui.card().classes('w-full'): + ui.label('Add Mode').classes('text-h6') + ui.markdown('**Allows duplicate** values.') + chips_add = ui.input_chips(label='Try typing "test" twice', new_value_mode='add') + ui.label().bind_text_from(chips_add, 'value', lambda v: f'Chips: {v}') + +ui.separator() + +# Add-unique mode +with ui.card().classes('w-full'): + ui.label('Add-Unique Mode').classes('text-h6') + ui.markdown('**Prevents duplicate** values.') + chips_unique = ui.input_chips(label='Try typing "test" twice', new_value_mode='add-unique') + ui.label().bind_text_from(chips_unique, 'value', lambda v: f'Chips: {v}') + +ui.separator() + +ui.button('Click to trigger blur', icon='touch_app') + +ui.run(port=8087)