Skip to content

Commit 513d21e

Browse files
Rishirandhawagradio-pr-botabidlabs
authored
feat: Add autoscroll parameter to HTML component (#11747)
* feat: Add autoscroll parameter to HTML component - Add autoscroll parameter to gr.HTML() component with default value True - Implement auto-scroll functionality in frontend when content changes - Add comprehensive tests for autoscroll parameter - Include demo script showcasing the feature Resolves: #7735 The autoscroll parameter allows users to control whether the HTML component automatically scrolls to the bottom when content is updated, similar to the existing autoscroll behavior in the Chatbot component. Example usage: gr.HTML('content', autoscroll=False) # Disable autoscroll gr.HTML('content', autoscroll=True) # Enable autoscroll (default) * add changeset * test html * fix * changes * autoscroll * format * changes --------- Co-authored-by: gradio-pr-bot <[email protected]> Co-authored-by: Abubakar Abid <[email protected]>
1 parent f53b24b commit 513d21e

File tree

7 files changed

+94
-1
lines changed

7 files changed

+94
-1
lines changed

.changeset/better-ants-wash.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@gradio/html": minor
3+
"gradio": minor
4+
---
5+
6+
feat:feat: Add autoscroll parameter to HTML component

demo/html_autoscroll/run.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: html_autoscroll"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import time\n", "\n", "def longer(val):\n", " for i in range(10):\n", " val = val + f\"<p>This is paragraph {i+1}.</p>\"\n", " time.sleep(0.2)\n", " yield val\n", "\n", "with gr.Blocks() as demo:\n", " h = gr.HTML(value=\"<p>This is a paragraph 0.</p>\", max_height=200, autoscroll=True)\n", " demo.load(longer, h, h)\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

demo/html_autoscroll/run.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import gradio as gr
2+
import time
3+
4+
def longer(val):
5+
for i in range(10):
6+
val = val + f"<p>This is paragraph {i+1}.</p>"
7+
time.sleep(0.2)
8+
yield val
9+
10+
with gr.Blocks() as demo:
11+
h = gr.HTML(value="<p>This is a paragraph 0.</p>", max_height=200, autoscroll=True)
12+
demo.load(longer, h, h)
13+
14+
demo.launch()

gradio/components/html.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(
4444
max_height: int | None = None,
4545
container: bool = False,
4646
padding: bool = True,
47+
autoscroll: bool = False,
4748
):
4849
"""
4950
Parameters:
@@ -62,10 +63,12 @@ def __init__(
6263
max_height: The maximum height of the component, specified in pixels if a number is passed, or in CSS units if a string is passed. If content exceeds the height, the component will scroll.
6364
container: If True, the HTML component will be displayed in a container. Default is False.
6465
padding: If True, the HTML component will have a certain padding (set by the `--block-padding` CSS variable) in all directions. Default is True.
66+
autoscroll: If True, will automatically scroll to the bottom of the component when the content changes, unless the user has scrolled up. If False, will not scroll to the bottom when the content changes.
6567
"""
6668
self.min_height = min_height
6769
self.max_height = max_height
6870
self.padding = padding
71+
self.autoscroll = autoscroll
6972
super().__init__(
7073
label=label,
7174
every=every,

js/html/Index.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
export let max_height: number | undefined = undefined;
2424
export let container = false;
2525
export let padding = true;
26+
export let autoscroll = false;
2627
</script>
2728

2829
<Block {visible} {elem_id} {elem_classes} {container} padding={false}>
@@ -50,6 +51,7 @@
5051
{value}
5152
{elem_classes}
5253
{visible}
54+
{autoscroll}
5355
on:change={() => gradio.dispatch("change")}
5456
on:click={() => gradio.dispatch("click")}
5557
/>

js/html/shared/HTML.svelte

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,86 @@
11
<script lang="ts">
2-
import { createEventDispatcher } from "svelte";
2+
import { createEventDispatcher, onMount, tick } from "svelte";
33
44
export let elem_classes: string[] = [];
55
export let value: string;
66
export let visible = true;
7+
export let autoscroll = false;
78
89
const dispatch = createEventDispatcher<{
910
change: undefined;
1011
click: undefined;
1112
}>();
1213
14+
let div: HTMLDivElement;
15+
let scrollable_parent: HTMLElement | null = null;
16+
17+
function get_scrollable_parent(element: HTMLElement): HTMLElement | null {
18+
let parent = element.parentElement;
19+
while (parent) {
20+
const style = window.getComputedStyle(parent);
21+
if (
22+
style.overflow === "auto" ||
23+
style.overflow === "scroll" ||
24+
style.overflowY === "auto" ||
25+
style.overflowY === "scroll"
26+
) {
27+
return parent;
28+
}
29+
parent = parent.parentElement;
30+
}
31+
return null;
32+
}
33+
34+
function is_at_bottom(): boolean {
35+
if (!div) return true;
36+
if (!scrollable_parent) {
37+
return (
38+
window.innerHeight + window.scrollY >=
39+
document.documentElement.scrollHeight - 100
40+
);
41+
}
42+
return (
43+
scrollable_parent.offsetHeight + scrollable_parent.scrollTop >=
44+
scrollable_parent.scrollHeight - 100
45+
);
46+
}
47+
48+
function scroll_to_bottom(): void {
49+
if (!div) return;
50+
if (scrollable_parent) {
51+
scrollable_parent.scrollTo(0, scrollable_parent.scrollHeight);
52+
} else {
53+
window.scrollTo(0, document.documentElement.scrollHeight);
54+
}
55+
}
56+
57+
async function scroll_on_value_update(): Promise<void> {
58+
if (!autoscroll || !div) return;
59+
if (!scrollable_parent) {
60+
scrollable_parent = get_scrollable_parent(div);
61+
}
62+
if (is_at_bottom()) {
63+
await new Promise((resolve) => setTimeout(resolve, 300));
64+
scroll_to_bottom();
65+
}
66+
}
67+
68+
onMount(() => {
69+
if (autoscroll) {
70+
scroll_to_bottom();
71+
}
72+
scroll_on_value_update();
73+
});
74+
1375
$: value, dispatch("change");
76+
$: if (value && autoscroll) {
77+
scroll_on_value_update();
78+
}
1479
</script>
1580

1681
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
1782
<div
83+
bind:this={div}
1884
class="prose {elem_classes.join(' ')}"
1985
class:hide={!visible}
2086
on:click={() => dispatch("click")}

test/components/test_html.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def test_component_functions(self):
2323
"max_height": None,
2424
"container": False,
2525
"padding": True,
26+
"autoscroll": False,
2627
}
2728

2829
def test_in_interface(self):

0 commit comments

Comments
 (0)