Skip to content

Commit bc07d2c

Browse files
authored
fix(chat): Adjust scroll bar positioning (#1861)
1 parent ac375b3 commit bc07d2c

File tree

7 files changed

+62
-23
lines changed

7 files changed

+62
-23
lines changed

.github/workflows/verify-js-built.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: |
4040
if [[ `git status --porcelain` ]]; then
4141
git diff
42-
echo "Uncommitted changes found. Please commit any changes that result from 'npm run build'."
42+
echo "Uncommitted changes found. Please commit any changes that result from 'npm ci && npm run build'."
4343
exit 1
4444
else
4545
echo "No uncommitted changes found."

js/chat/chat.scss

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ shiny-chat-container {
33
--shiny-chat-user-message-bg: RGBA(var(--bs-primary-rgb, 0, 123, 194), 0.06);
44
--_chat-container-padding: 0.25rem;
55

6-
display: flex;
7-
flex-direction: column;
6+
display: grid;
7+
grid-template-columns: 1fr;
8+
grid-template-rows: 1fr auto;
89
margin: 0 auto;
9-
gap: 1rem;
10-
overflow: auto;
10+
gap: 0;
1111
padding: var(--_chat-container-padding);
1212
padding-bottom: 0; // Bottom padding is on input element
1313

@@ -54,6 +54,13 @@ shiny-chat-messages {
5454
display: flex;
5555
flex-direction: column;
5656
gap: 2rem;
57+
overflow: auto;
58+
margin-bottom: 1rem;
59+
60+
// Make space for the scroll bar
61+
--_scroll-margin: 1rem;
62+
padding-right: var(--_scroll-margin);
63+
margin-right: calc(-1 * var(--_scroll-margin));
5764
}
5865

5966
shiny-chat-message {
@@ -96,13 +103,12 @@ shiny-chat-message {
96103
}
97104

98105
shiny-chat-input {
99-
--_input-padding-top: 1rem;
106+
--_input-padding-top: 0;
100107
--_input-padding-bottom: var(--_chat-container-padding, 0.25rem);
101108

102-
margin-top: auto;
109+
margin-top: calc(-1 * var(--_input-padding-top));
103110
position: sticky;
104-
bottom: 0;
105-
background: linear-gradient(to bottom, transparent, var(--bs-body-bg, white) calc(var(--_input-padding-top) - var(--_input-padding-bottom)));
111+
bottom: calc(-1 * var(--_input-padding-bottom));
106112
padding-block: var(--_input-padding-top) var(--_input-padding-bottom);
107113

108114
textarea {

js/chat/chat.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ class ChatInput extends LightElement {
262262
}
263263

264264
class ChatContainer extends LightElement {
265+
inputSentinelObserver?: IntersectionObserver;
265266

266267
private get input(): ChatInput {
267268
return this.querySelector(CHAT_INPUT_TAG) as ChatInput;
@@ -280,6 +281,35 @@ class ChatContainer extends LightElement {
280281
return html``;
281282
}
282283

284+
connectedCallback(): void {
285+
super.connectedCallback();
286+
287+
// We use a sentinel element that we place just above the shiny-chat-input. When it
288+
// moves off-screen we know that the text area input is now floating, add shadow.
289+
let sentinel = this.querySelector<HTMLElement>("div");
290+
if (!sentinel) {
291+
sentinel = createElement("div", {
292+
style: "width: 100%; height: 0;",
293+
}) as HTMLElement;
294+
this.input.insertAdjacentElement("afterend", sentinel);
295+
}
296+
297+
this.inputSentinelObserver = new IntersectionObserver(
298+
(entries) => {
299+
const inputTextarea = this.input.querySelector("textarea");
300+
if (!inputTextarea) return;
301+
const addShadow = entries[0]?.intersectionRatio === 0;
302+
inputTextarea.classList.toggle("shadow", addShadow);
303+
},
304+
{
305+
threshold: [0, 1],
306+
rootMargin: "0px",
307+
}
308+
);
309+
310+
this.inputSentinelObserver.observe(sentinel);
311+
}
312+
283313
firstUpdated(): void {
284314
// Don't attach event listeners until child elements are rendered
285315
if (!this.messages) return;
@@ -306,6 +336,9 @@ class ChatContainer extends LightElement {
306336
disconnectedCallback(): void {
307337
super.disconnectedCallback();
308338

339+
this.inputSentinelObserver?.disconnect();
340+
this.inputSentinelObserver = undefined;
341+
309342
this.removeEventListener("shiny-chat-input-sent", this.#onInputSent);
310343
this.removeEventListener("shiny-chat-append-message", this.#onAppend);
311344
this.removeEventListener(

shiny/www/py-shiny/chat/chat.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shiny/www/py-shiny/chat/chat.css.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)