Skip to content

Commit

Permalink
bug(input_text_area): Auto resized text areas resize on visibility ch…
Browse files Browse the repository at this point in the history
…ange (#1569)

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
  • Loading branch information
schloerke and cpsievert authored Jul 26, 2024
1 parent 8b65ca3 commit 97dd4a7
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

* `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)

### Bug fixes

### Deprecations
Expand Down
42 changes: 39 additions & 3 deletions js/text-area/textarea-autoresize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,46 @@ function onDelegatedEvent(
});
}

// Use a single intersectionObserver as they are slow to create / use.
let textAreaIntersectionObserver: null | IntersectionObserver = null;

function callUpdateHeightWhenTargetIsVisible(target: HTMLTextAreaElement) {
if (textAreaIntersectionObserver === null) {
// Create a single observer to watch for the textarea becoming visible
textAreaIntersectionObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
// Quit if the entry is not visible
if (!entry.isIntersecting) {
return;
}
// If the entry is visible (even if it's just a single pixel)
// Stop observing the target
textAreaIntersectionObserver!.unobserve(entry.target);

// Update the height of the textarea
update_height(entry.target as HTMLTextAreaElement);
});
}
);
}

textAreaIntersectionObserver.observe(target);
}

function update_height(target: HTMLTextAreaElement) {
// Automatically resize the textarea to fit its content.
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
if (target.scrollHeight > 0) {
// Automatically resize the textarea to fit its content.
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
} else {
// The textarea is not visible on the page, therefore it has a 0 scroll height.

// If we should autoresize the text area height, then we can wait for the textarea to
// become visible and call `update_height` again. Hopefully the scroll height is no
// longer 0
callUpdateHeightWhenTargetIsVisible(target);
}
}

// Update on change
Expand Down
25 changes: 23 additions & 2 deletions shiny/www/py-shiny/text-area/textarea-autoresize.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,30 @@ function onDelegatedEvent(eventName, selector, callback) {
}
});
}
var textAreaIntersectionObserver = null;
function callUpdateHeightWhenTargetIsVisible(target) {
if (textAreaIntersectionObserver === null) {
textAreaIntersectionObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
}
textAreaIntersectionObserver.unobserve(entry.target);
update_height(entry.target);
});
}
);
}
textAreaIntersectionObserver.observe(target);
}
function update_height(target) {
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
if (target.scrollHeight > 0) {
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
} else {
callUpdateHeightWhenTargetIsVisible(target);
}
}
onDelegatedEvent(
"input",
Expand Down
26 changes: 26 additions & 0 deletions tests/playwright/shiny/inputs/input_text_area/autoresize/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from shiny.express import render, ui

with ui.navset_card_tab(id="tab"):

with ui.nav_panel("Tab 1"):
"Tab 1 content"
with ui.nav_panel("Text Area"):
ui.input_text_area(
id="test_text_area",
label="A text area input",
autoresize=True,
value="a\nb\nc\nd\ne",
)

ui.input_text_area(
id="test_text_area2",
label="A second text area input",
autoresize=True,
value="a\nb\nc\nd\ne",
rows=4,
)


@render.code
def text():
return "Loaded"
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from playwright.sync_api import Page

from shiny.playwright import controller
from shiny.playwright.expect import expect_to_have_style
from shiny.run import ShinyAppProc


def test_accordion(page: Page, local_app: ShinyAppProc, is_webkit: bool) -> None:
page.goto(local_app.url)

text = controller.OutputCode(page, "text")
tab = controller.NavsetTab(page, "tab")

test_text_area = controller.InputTextArea(page, "test_text_area")
test_text_area_w_rows = controller.InputTextArea(page, "test_text_area2")

text.expect_value("Loaded")

# Make sure the `rows` is respected
test_text_area_w_rows.expect_rows("4")
# Make sure the placeholder row value of `1` is set
test_text_area.expect_rows("1")

tab.set("Text Area")

test_text_area.expect_autoresize(True)
test_text_area.expect_value("a\nb\nc\nd\ne")

if is_webkit:
# Skip the rest of the test for webkit.
# Heights are not consistent with chrome and firefox
return
expect_to_have_style(test_text_area.loc, "height", "125px")
expect_to_have_style(test_text_area_w_rows.loc, "height", "125px")

# Make sure the `rows` is consistent
test_text_area.expect_rows("1")
test_text_area_w_rows.expect_rows("4")

# Reset the text area to a single row and make sure the area shrink to appropriate size
test_text_area.set("single row")
test_text_area_w_rows.set("single row")

# 1 row
expect_to_have_style(test_text_area.loc, "height", "35px")
# 4 rows
expect_to_have_style(test_text_area_w_rows.loc, "height", "102px")

0 comments on commit 97dd4a7

Please sign in to comment.