Skip to content

Commit 97dd4a7

Browse files
schloerkecpsievert
andauthored
bug(input_text_area): Auto resized text areas resize on visibility change (posit-dev#1569)
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
1 parent 8b65ca3 commit 97dd4a7

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020
* Require shinyswatch >= 0.7.0 and updated examples accordingly. (#1558)
2121

22+
* `input_text_area(autoresize=True)` now resizes properly even when it's not visible when initially rendered (e.g. in a closed accordion or a hidden tab). (#1560)
23+
2224
### Bug fixes
2325

2426
### Deprecations

js/text-area/textarea-autoresize.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,46 @@ function onDelegatedEvent(
1818
});
1919
}
2020

21+
// Use a single intersectionObserver as they are slow to create / use.
22+
let textAreaIntersectionObserver: null | IntersectionObserver = null;
23+
24+
function callUpdateHeightWhenTargetIsVisible(target: HTMLTextAreaElement) {
25+
if (textAreaIntersectionObserver === null) {
26+
// Create a single observer to watch for the textarea becoming visible
27+
textAreaIntersectionObserver = new IntersectionObserver(
28+
(entries, observer) => {
29+
entries.forEach((entry) => {
30+
// Quit if the entry is not visible
31+
if (!entry.isIntersecting) {
32+
return;
33+
}
34+
// If the entry is visible (even if it's just a single pixel)
35+
// Stop observing the target
36+
textAreaIntersectionObserver!.unobserve(entry.target);
37+
38+
// Update the height of the textarea
39+
update_height(entry.target as HTMLTextAreaElement);
40+
});
41+
}
42+
);
43+
}
44+
45+
textAreaIntersectionObserver.observe(target);
46+
}
47+
2148
function update_height(target: HTMLTextAreaElement) {
22-
// Automatically resize the textarea to fit its content.
23-
target.style.height = "auto";
24-
target.style.height = target.scrollHeight + "px";
49+
if (target.scrollHeight > 0) {
50+
// Automatically resize the textarea to fit its content.
51+
target.style.height = "auto";
52+
target.style.height = target.scrollHeight + "px";
53+
} else {
54+
// The textarea is not visible on the page, therefore it has a 0 scroll height.
55+
56+
// If we should autoresize the text area height, then we can wait for the textarea to
57+
// become visible and call `update_height` again. Hopefully the scroll height is no
58+
// longer 0
59+
callUpdateHeightWhenTargetIsVisible(target);
60+
}
2561
}
2662

2763
// Update on change

shiny/www/py-shiny/text-area/textarea-autoresize.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,30 @@ function onDelegatedEvent(eventName, selector, callback) {
77
}
88
});
99
}
10+
var textAreaIntersectionObserver = null;
11+
function callUpdateHeightWhenTargetIsVisible(target) {
12+
if (textAreaIntersectionObserver === null) {
13+
textAreaIntersectionObserver = new IntersectionObserver(
14+
(entries, observer) => {
15+
entries.forEach((entry) => {
16+
if (!entry.isIntersecting) {
17+
return;
18+
}
19+
textAreaIntersectionObserver.unobserve(entry.target);
20+
update_height(entry.target);
21+
});
22+
}
23+
);
24+
}
25+
textAreaIntersectionObserver.observe(target);
26+
}
1027
function update_height(target) {
11-
target.style.height = "auto";
12-
target.style.height = target.scrollHeight + "px";
28+
if (target.scrollHeight > 0) {
29+
target.style.height = "auto";
30+
target.style.height = target.scrollHeight + "px";
31+
} else {
32+
callUpdateHeightWhenTargetIsVisible(target);
33+
}
1334
}
1435
onDelegatedEvent(
1536
"input",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from shiny.express import render, ui
2+
3+
with ui.navset_card_tab(id="tab"):
4+
5+
with ui.nav_panel("Tab 1"):
6+
"Tab 1 content"
7+
with ui.nav_panel("Text Area"):
8+
ui.input_text_area(
9+
id="test_text_area",
10+
label="A text area input",
11+
autoresize=True,
12+
value="a\nb\nc\nd\ne",
13+
)
14+
15+
ui.input_text_area(
16+
id="test_text_area2",
17+
label="A second text area input",
18+
autoresize=True,
19+
value="a\nb\nc\nd\ne",
20+
rows=4,
21+
)
22+
23+
24+
@render.code
25+
def text():
26+
return "Loaded"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from playwright.sync_api import Page
2+
3+
from shiny.playwright import controller
4+
from shiny.playwright.expect import expect_to_have_style
5+
from shiny.run import ShinyAppProc
6+
7+
8+
def test_accordion(page: Page, local_app: ShinyAppProc, is_webkit: bool) -> None:
9+
page.goto(local_app.url)
10+
11+
text = controller.OutputCode(page, "text")
12+
tab = controller.NavsetTab(page, "tab")
13+
14+
test_text_area = controller.InputTextArea(page, "test_text_area")
15+
test_text_area_w_rows = controller.InputTextArea(page, "test_text_area2")
16+
17+
text.expect_value("Loaded")
18+
19+
# Make sure the `rows` is respected
20+
test_text_area_w_rows.expect_rows("4")
21+
# Make sure the placeholder row value of `1` is set
22+
test_text_area.expect_rows("1")
23+
24+
tab.set("Text Area")
25+
26+
test_text_area.expect_autoresize(True)
27+
test_text_area.expect_value("a\nb\nc\nd\ne")
28+
29+
if is_webkit:
30+
# Skip the rest of the test for webkit.
31+
# Heights are not consistent with chrome and firefox
32+
return
33+
expect_to_have_style(test_text_area.loc, "height", "125px")
34+
expect_to_have_style(test_text_area_w_rows.loc, "height", "125px")
35+
36+
# Make sure the `rows` is consistent
37+
test_text_area.expect_rows("1")
38+
test_text_area_w_rows.expect_rows("4")
39+
40+
# Reset the text area to a single row and make sure the area shrink to appropriate size
41+
test_text_area.set("single row")
42+
test_text_area_w_rows.set("single row")
43+
44+
# 1 row
45+
expect_to_have_style(test_text_area.loc, "height", "35px")
46+
# 4 rows
47+
expect_to_have_style(test_text_area_w_rows.loc, "height", "102px")

0 commit comments

Comments
 (0)