+19
-7
src/components/atoms/grain-scroll-to-top.js
+19
-7
src/components/atoms/grain-scroll-to-top.js
···
8
8
9
9
static styles = css`
10
10
:host {
11
-
position: fixed;
12
-
bottom: 20px;
13
-
left: 20px;
11
+
position: sticky;
12
+
bottom: var(--space-sm);
13
+
left: 0;
14
+
align-self: flex-start;
14
15
z-index: 100;
16
+
margin-top: auto;
17
+
margin-left: var(--space-sm);
18
+
}
19
+
@media (min-width: 768px) {
20
+
:host {
21
+
position: fixed;
22
+
bottom: calc(57px + env(safe-area-inset-bottom, 0px) + var(--space-lg));
23
+
left: calc(50% - var(--feed-max-width) / 2 - 64px);
24
+
margin-left: 0;
25
+
margin-top: 0;
26
+
}
15
27
}
16
28
button {
17
29
display: flex;
18
30
align-items: center;
19
31
justify-content: center;
20
-
width: 48px;
21
-
height: 48px;
32
+
width: 42px;
33
+
height: 42px;
22
34
border-radius: 50%;
23
35
border: 1px solid var(--color-border);
24
-
background: var(--color-surface-secondary);
25
-
color: var(--color-accent);
36
+
background: var(--color-bg-primary);
37
+
color: white;
26
38
cursor: pointer;
27
39
opacity: 0;
28
40
pointer-events: none;
+31
-8
src/components/pages/grain-timeline.js
+31
-8
src/components/pages/grain-timeline.js
···
47
47
#observer = null;
48
48
#initialized = false;
49
49
#boundHandleScroll = null;
50
+
#scrollContainer = null;
50
51
51
52
constructor() {
52
53
super();
···
91
92
this.#fetchTimeline();
92
93
}
93
94
this.#boundHandleScroll = this.#handleScroll.bind(this);
94
-
window.addEventListener('scroll', this.#boundHandleScroll, { passive: true });
95
+
this.#scrollContainer = this.#findScrollContainer();
96
+
(this.#scrollContainer || window).addEventListener('scroll', this.#boundHandleScroll, { passive: true });
97
+
}
98
+
99
+
#findScrollContainer() {
100
+
let el = this;
101
+
while (el) {
102
+
const parent = el.parentElement || el.getRootNode()?.host;
103
+
if (!parent || parent === document.documentElement) break;
104
+
105
+
const style = getComputedStyle(parent);
106
+
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
107
+
return parent;
108
+
}
109
+
el = parent;
110
+
}
111
+
return null;
95
112
}
96
113
97
114
disconnectedCallback() {
98
115
super.disconnectedCallback();
99
116
this.#observer?.disconnect();
100
117
if (this.#boundHandleScroll) {
101
-
window.removeEventListener('scroll', this.#boundHandleScroll);
118
+
(this.#scrollContainer || window).removeEventListener('scroll', this.#boundHandleScroll);
102
119
}
103
120
}
104
121
···
203
220
}
204
221
205
222
#handleScroll() {
206
-
this._showScrollTop = window.scrollY > 150;
223
+
const scrollTop = this.#scrollContainer ? this.#scrollContainer.scrollTop : window.scrollY;
224
+
this._showScrollTop = scrollTop > 150;
207
225
}
208
226
209
227
async #handleScrollTop() {
210
228
if (this._refreshing) return;
211
229
212
-
window.scrollTo({ top: 0, behavior: 'smooth' });
230
+
if (this.#scrollContainer) {
231
+
this.#scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
232
+
} else {
233
+
window.scrollTo({ top: 0, behavior: 'smooth' });
234
+
}
213
235
214
236
// Wait for scroll to complete before refreshing
215
237
await new Promise(resolve => setTimeout(resolve, 400));
···
249
271
focusPhotoUrl=${this._focusPhotoUrl || ''}
250
272
@close=${this.#handleCommentSheetClose}
251
273
></grain-comment-sheet>
274
+
275
+
<grain-scroll-to-top
276
+
?visible=${this._showScrollTop}
277
+
@scroll-top=${this.#handleScrollTop}
278
+
></grain-scroll-to-top>
252
279
</grain-feed-layout>
253
-
<grain-scroll-to-top
254
-
?visible=${this._showScrollTop}
255
-
@scroll-top=${this.#handleScrollTop}
256
-
></grain-scroll-to-top>
257
280
`;
258
281
}
259
282
}