Repository: gradio-app/gradio
Pull request: Add blur event to textbox and number
# Description
Add blur event. Triggered when user clicks out of textbox.

https://user-images.githubusercontent.com/12725292/195451431-d652365b-ded9-46ec-a6ec-454835fd0679.mov



Please include: 
* relevant motivation
* a summary of the change 
* which issue is fixed. 
* any additional dependencies that are required for this change.

Closes: #2449

# Checklist:

- [ ] I have performed a self-review of my own code
- [ ] I have added a short summary of my change to the CHANGELOG.md
- [ ] My code follows the style guidelines of this project
- [ ] I have commented my code in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


# A note about the CHANGELOG

Hello 👋 and thank you for contributing to Gradio!

All pull requests must update the change log located in CHANGELOG.md, unless the pull request is labeled with the "no-changelog-update" label.

Please add a brief summary of the change to the Upcoming Release > Full Changelog section of the CHANGELOG.md file and include
a link to the PR (formatted in markdown) and a link to your github profile (if you like). For example, "* Added a cool new feature by `[@myusername](link-to-your-github-profile)` in `[PR 11111](https://github.com/gradio-app/gradio/pull/11111)`".

If you would like to elaborate on your change further, feel free to include a longer explanation in the other sections.
If you would like an image/gif/video showcasing your feature, it may be best to edit the CHANGELOG file using the 
GitHub web UI since that lets you upload files directly via drag-and-drop.

Problem statement:
Add blur event trigger for textbox and number components
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**  
A clear and concise description of what you want to happen.

**Additional context**  
Add any other context or screenshots about the feature request here.

Hints from issue discussion:

Commit message: changelog

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e6fefcd26..22397b52ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -93,6 +93,7 @@ No changes to highlight.
 * Fix bug where errors would cause apps run in reload mode to hang forever by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2394](https://github.com/gradio-app/gradio/pull/2394)
 * Fix bug where new typeable slider doesn't respect the minimum and maximum values [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2380](https://github.com/gradio-app/gradio/pull/2380)
 * Add guide on creating a map demo using the `gr.Plot()` component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2402](https://github.com/gradio-app/gradio/pull/2402)
+* Add blur event for `Textbox` and `Number` components [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2448](https://github.com/gradio-app/gradio/pull/2448)
 
 ## Contributors Shoutout:
 No changes to highlight.
diff --git a/demo/blocks_component_shortcut/run.py b/demo/blocks_component_shortcut/run.py
index 6b408fdaf2..6e0b2f6a33 100644
--- a/demo/blocks_component_shortcut/run.py
+++ b/demo/blocks_component_shortcut/run.py
@@ -22,9 +22,9 @@ def greet(str):
             text1 = gr.component("textarea")
             text2 = gr.TextArea()
             text3 = gr.templates.TextArea()
-    text1.change(greet, text1, text2)
-    text2.change(greet, text2, text3)
-    text3.change(greet, text3, text1)
+    text1.blur(greet, text1, text2)
+    text2.blur(greet, text2, text3)
+    text3.blur(greet, text3, text1)
     button = gr.component("button")
 
 if __name__ == "__main__":
diff --git a/gradio/components.py b/gradio/components.py
index ae087c5ea7..b4393f09fd 100644
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -47,6 +47,7 @@ class DataframeData(TypedDict):
     Playable,
     Streamable,
     Submittable,
+    Blurable,
 )
 from gradio.layouts import Column, Form, Row
 from gradio.serializing import (
@@ -253,8 +254,8 @@ class FormComponent:
     expected_parent = Form
 
 
-@document("change", "submit", "style")
-class Textbox(Changeable, Submittable, IOComponent, SimpleSerializable, FormComponent):
+@document("change", "submit", "blur", "style")
+class Textbox(Changeable, Submittable, Blurable, IOComponent, SimpleSerializable, FormComponent):
     """
     Creates a textarea for user to enter string input or display string output.
     Preprocessing: passes textarea value as a {str} into the function.
@@ -420,7 +421,7 @@ def get_interpretation_scores(
 
 
 @document("change", "submit", "style")
-class Number(Changeable, Submittable, IOComponent, SimpleSerializable, FormComponent):
+class Number(Changeable, Submittable, Blurable, IOComponent, SimpleSerializable, FormComponent):
     """
     Creates a numeric field for user to enter numbers as input or display numeric output.
     Preprocessing: passes field value as a {float} or {int} into the function, depending on `precision`.
diff --git a/gradio/events.py b/gradio/events.py
index c52c4f70d3..7f05437711 100644
--- a/gradio/events.py
+++ b/gradio/events.py
@@ -465,3 +465,52 @@ def stream(
             postprocess=postprocess,
             queue=queue,
         )
+
+class Blurable(Block):
+    def blur(
+        self,
+        fn: Callable,
+        inputs: List[Component],
+        outputs: List[Component],
+        api_name: AnyStr = None,
+        status_tracker: Optional[StatusTracker] = None,
+        scroll_to_output: bool = False,
+        show_progress: bool = True,
+        queue: Optional[bool] = None,
+        preprocess: bool = True,
+        postprocess: bool = True,
+        _js: Optional[str] = None,
+    ):
+        """
+        This event is triggered when the component's is unfocused/blurred (e.g. when the user clicks outside of a textbox). This method can be used when this component is in a Gradio Blocks.
+
+        Parameters:
+            fn: Callable function
+            inputs: List of inputs
+            outputs: List of outputs
+            api_name: Defining this parameter exposes the endpoint in the api docs
+            scroll_to_output: If True, will scroll to output component on completion
+            show_progress: If True, will show progress animation while pending
+            queue: If True, will place the request on the queue, if the queue exists
+            preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
+            postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser.
+        """
+        # _js: Optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
+        if status_tracker:
+            warnings.warn(
+                "The 'status_tracker' parameter has been deprecated and has no effect."
+            )
+
+        self.set_event_trigger(
+            "blur",
+            fn,
+            inputs,
+            outputs,
+            api_name=api_name,
+            scroll_to_output=scroll_to_output,
+            show_progress=show_progress,
+            js=_js,
+            preprocess=preprocess,
+            postprocess=postprocess,
+            queue=queue,
+        )
diff --git a/ui/packages/app/src/components/Textbox/Textbox.svelte b/ui/packages/app/src/components/Textbox/Textbox.svelte
index 664d19672f..fac04a7d7a 100644
--- a/ui/packages/app/src/components/Textbox/Textbox.svelte
+++ b/ui/packages/app/src/components/Textbox/Textbox.svelte
@@ -39,6 +39,7 @@
 		{placeholder}
 		on:change
 		on:submit
+		on:blur
 		disabled={mode === "static"}
 	/>
 </Block>
diff --git a/ui/packages/form/src/Textbox.svelte b/ui/packages/form/src/Textbox.svelte
index 4adee0dab8..465e966bd0 100644
--- a/ui/packages/form/src/Textbox.svelte
+++ b/ui/packages/form/src/Textbox.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
 	import { createEventDispatcher, tick } from "svelte";
 	import { BlockTitle } from "@gradio/atoms";
-	import type { Styles } from "@gradio/utils";
 
 	export let value: string = "";
 	export let lines: number = 1;
@@ -19,12 +18,17 @@
 	const dispatch = createEventDispatcher<{
 		change: string;
 		submit: undefined;
+		blur: undefined;
 	}>();
 
 	function handle_change(val: string) {
 		dispatch("change", val);
 	}
 
+	function handle_blur(e: FocusEvent) {
+		dispatch("blur");
+	}
+
 	async function handle_keypress(e: KeyboardEvent) {
 		await tick();
 		if (e.key === "Enter" && e.shiftKey && lines > 1) {
@@ -98,6 +102,7 @@
 			{placeholder}
 			{disabled}
 			on:keypress={handle_keypress}
+			on:blur={handle_blur}
 		/>
 	{:else}
 		<textarea
@@ -110,6 +115,7 @@
 			rows={lines}
 			{disabled}
 			on:keypress={handle_keypress}
+			on:blur={handle_blur}
 		/>
 	{/if}
 </label>
diff --git a/website/homepage/src/docs/__init__.py b/website/homepage/src/docs/__init__.py
index 38c9dc8956..10f1f61b1b 100644
--- a/website/homepage/src/docs/__init__.py
+++ b/website/homepage/src/docs/__init__.py
@@ -1,7 +1,7 @@
 import os
 from gradio.documentation import generate_documentation, document_cls
 import gradio.templates
-from gradio.events import Changeable, Clearable, Submittable, Editable, Playable, Clickable
+from gradio.events import Changeable, Clearable, Submittable, Editable, Playable, Clickable, Blurable
 from ..guides import guides
 
 DIR = os.path.dirname(__file__)
@@ -56,6 +56,8 @@ def add_supported_events():
             component["events"].append("edit()")
         if issubclass(component["class"], Submittable):
             component["events"].append("submit()")
+        if issubclass(component["class"], Blurable):
+                component["events"].append("blur()")
         if component["events"]:
             component["events"] = ", ".join(component["events"])
 


Code review comments:
diff_hunk: @@ -465,3 +465,52 @@ def stream(
             postprocess=postprocess,
             queue=queue,
         )
+
+class Blurable(Block):
+    def blur(
+        self,
+        fn: Callable,
+        inputs: List[Component],
+        outputs: List[Component],
+        api_name: AnyStr = None,
+        status_tracker: Optional[StatusTracker] = None,
+        scroll_to_output: bool = False,
+        show_progress: bool = True,
+        queue: Optional[bool] = None,
+        preprocess: bool = True,
+        postprocess: bool = True,
+        _js: Optional[str] = None,
+    ):
+        """
+        This event is triggered when the component's is unfocused/blurred (e.g. when the user clicks outside of a textbox). This method can be used when this component is in a Gradio Blocks.
+
+        Parameters:
+            fn: Callable function
+            inputs: List of inputs
+            outputs: List of outputs
+            api_name: Defining this parameter exposes the endpoint in the api docs
+            scroll_to_output: If True, will scroll to output component on completion
+            show_progress: If True, will show progress animation while pending
+            queue: If True, will place the request on the queue, if the queue exists
+            preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
+            postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser.
+        """
+        # _js: Optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
+        if status_tracker:
path: gradio/events.py
text: @user1:
remove deprecated arguments in new code

diff_hunk: @@ -47,6 +47,7 @@ class DataframeData(TypedDict):
     Playable,
     Streamable,
     Submittable,
+    Blurable,
path: gradio/components.py
text: @user1:
according to wiktionary, should be "Blurrable" (two r's)