@bbki.ng/bb-msg-history 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component.d.ts +3 -0
- package/dist/component.js +34 -5
- package/package.json +1 -1
- package/src/component.ts +43 -5
package/dist/component.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export declare class BBMsgHistory extends HTMLElement {
|
|
|
5
5
|
private _lastAuthor;
|
|
6
6
|
private _lastGroupTimestamp;
|
|
7
7
|
private _scrollButtonVisible;
|
|
8
|
+
private _userHasScrolledManually;
|
|
9
|
+
private _isProgrammaticScroll;
|
|
10
|
+
private _lastScrollTop;
|
|
8
11
|
static get observedAttributes(): string[];
|
|
9
12
|
constructor();
|
|
10
13
|
attributeChangedCallback(name: string): void;
|
package/dist/component.js
CHANGED
|
@@ -13,6 +13,9 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
13
13
|
this._userAuthors = new Map();
|
|
14
14
|
this._lastAuthor = '';
|
|
15
15
|
this._scrollButtonVisible = false;
|
|
16
|
+
this._userHasScrolledManually = false;
|
|
17
|
+
this._isProgrammaticScroll = false;
|
|
18
|
+
this._lastScrollTop = 0;
|
|
16
19
|
this.attachShadow({ mode: 'open' });
|
|
17
20
|
}
|
|
18
21
|
attributeChangedCallback(name) {
|
|
@@ -115,10 +118,16 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
115
118
|
}
|
|
116
119
|
// Smooth scroll to bottom (skip in infinite mode)
|
|
117
120
|
if (!this.hasAttribute('infinite')) {
|
|
121
|
+
// Mark this as a programmatic scroll so the scroll handler ignores it
|
|
122
|
+
this._isProgrammaticScroll = true;
|
|
118
123
|
container.scrollTo({
|
|
119
124
|
top: container.scrollHeight,
|
|
120
125
|
behavior: 'smooth',
|
|
121
126
|
});
|
|
127
|
+
// Reset the flag after smooth scroll animation completes (~300ms)
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
this._isProgrammaticScroll = false;
|
|
130
|
+
}, 300);
|
|
122
131
|
// Hide scroll button since we're scrolling to bottom
|
|
123
132
|
const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
|
|
124
133
|
if (scrollButton && this._scrollButtonVisible) {
|
|
@@ -270,8 +279,13 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
270
279
|
const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
|
|
271
280
|
const isInfinite = this.hasAttribute('infinite');
|
|
272
281
|
if (container && !isInfinite) {
|
|
282
|
+
// Mark as programmatic scroll to prevent triggering user scroll detection
|
|
283
|
+
this._isProgrammaticScroll = true;
|
|
273
284
|
container.scrollTop = container.scrollHeight;
|
|
274
|
-
|
|
285
|
+
requestAnimationFrame(() => {
|
|
286
|
+
this._isProgrammaticScroll = false;
|
|
287
|
+
});
|
|
288
|
+
this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
|
|
275
289
|
}
|
|
276
290
|
if (scrollButton && !isInfinite) {
|
|
277
291
|
scrollButton.addEventListener('click', () => {
|
|
@@ -304,19 +318,34 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
304
318
|
`;
|
|
305
319
|
}
|
|
306
320
|
}
|
|
307
|
-
_setupScrollTracking(container, button) {
|
|
321
|
+
_setupScrollTracking(container, button, options) {
|
|
308
322
|
const checkScrollPosition = () => {
|
|
323
|
+
// Ignore programmatic scrolls - they don't indicate user intent
|
|
324
|
+
if (this._isProgrammaticScroll)
|
|
325
|
+
return;
|
|
326
|
+
// Mark that user has manually scrolled
|
|
327
|
+
if (!this._userHasScrolledManually) {
|
|
328
|
+
this._userHasScrolledManually = true;
|
|
329
|
+
}
|
|
330
|
+
const currentScrollTop = container.scrollTop;
|
|
331
|
+
const isScrollingUp = currentScrollTop < this._lastScrollTop;
|
|
332
|
+
this._lastScrollTop = currentScrollTop;
|
|
309
333
|
const threshold = 50; // pixels from bottom
|
|
310
334
|
const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
311
335
|
const hasOverflow = container.scrollHeight > container.clientHeight;
|
|
312
|
-
|
|
336
|
+
// Only show button when: user has scrolled, is not at bottom, is scrolling up
|
|
337
|
+
const shouldShow = !isAtBottom && hasOverflow && this._userHasScrolledManually && isScrollingUp;
|
|
313
338
|
if (shouldShow !== this._scrollButtonVisible) {
|
|
314
339
|
this._scrollButtonVisible = shouldShow;
|
|
315
340
|
button.classList.toggle('visible', shouldShow);
|
|
316
341
|
}
|
|
317
342
|
};
|
|
318
|
-
//
|
|
319
|
-
|
|
343
|
+
// Initialize last scroll position
|
|
344
|
+
this._lastScrollTop = container.scrollTop;
|
|
345
|
+
// Check initial state unless skipped
|
|
346
|
+
if (!options?.skipInitialCheck) {
|
|
347
|
+
checkScrollPosition();
|
|
348
|
+
}
|
|
320
349
|
// Listen for scroll events with passive listener for performance
|
|
321
350
|
container.addEventListener('scroll', checkScrollPosition, { passive: true });
|
|
322
351
|
// Also check on resize
|
package/package.json
CHANGED
package/src/component.ts
CHANGED
|
@@ -12,6 +12,9 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
12
12
|
private _lastAuthor = '';
|
|
13
13
|
private _lastGroupTimestamp: string | undefined;
|
|
14
14
|
private _scrollButtonVisible = false;
|
|
15
|
+
private _userHasScrolledManually = false;
|
|
16
|
+
private _isProgrammaticScroll = false;
|
|
17
|
+
private _lastScrollTop = 0;
|
|
15
18
|
|
|
16
19
|
static get observedAttributes() {
|
|
17
20
|
return ['theme', 'loading', 'hide-scroll-bar', 'infinite'];
|
|
@@ -149,11 +152,19 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
149
152
|
|
|
150
153
|
// Smooth scroll to bottom (skip in infinite mode)
|
|
151
154
|
if (!this.hasAttribute('infinite')) {
|
|
155
|
+
// Mark this as a programmatic scroll so the scroll handler ignores it
|
|
156
|
+
this._isProgrammaticScroll = true;
|
|
157
|
+
|
|
152
158
|
container.scrollTo({
|
|
153
159
|
top: container.scrollHeight,
|
|
154
160
|
behavior: 'smooth',
|
|
155
161
|
});
|
|
156
162
|
|
|
163
|
+
// Reset the flag after smooth scroll animation completes (~300ms)
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
this._isProgrammaticScroll = false;
|
|
166
|
+
}, 300);
|
|
167
|
+
|
|
157
168
|
// Hide scroll button since we're scrolling to bottom
|
|
158
169
|
const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement;
|
|
159
170
|
if (scrollButton && this._scrollButtonVisible) {
|
|
@@ -338,8 +349,13 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
338
349
|
const isInfinite = this.hasAttribute('infinite');
|
|
339
350
|
|
|
340
351
|
if (container && !isInfinite) {
|
|
352
|
+
// Mark as programmatic scroll to prevent triggering user scroll detection
|
|
353
|
+
this._isProgrammaticScroll = true;
|
|
341
354
|
container.scrollTop = container.scrollHeight;
|
|
342
|
-
|
|
355
|
+
requestAnimationFrame(() => {
|
|
356
|
+
this._isProgrammaticScroll = false;
|
|
357
|
+
});
|
|
358
|
+
this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
|
|
343
359
|
}
|
|
344
360
|
|
|
345
361
|
if (scrollButton && !isInfinite) {
|
|
@@ -376,13 +392,30 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
394
|
|
|
379
|
-
private _setupScrollTracking(
|
|
395
|
+
private _setupScrollTracking(
|
|
396
|
+
container: HTMLElement,
|
|
397
|
+
button: HTMLButtonElement,
|
|
398
|
+
options?: { skipInitialCheck?: boolean }
|
|
399
|
+
): void {
|
|
380
400
|
const checkScrollPosition = () => {
|
|
401
|
+
// Ignore programmatic scrolls - they don't indicate user intent
|
|
402
|
+
if (this._isProgrammaticScroll) return;
|
|
403
|
+
|
|
404
|
+
// Mark that user has manually scrolled
|
|
405
|
+
if (!this._userHasScrolledManually) {
|
|
406
|
+
this._userHasScrolledManually = true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const currentScrollTop = container.scrollTop;
|
|
410
|
+
const isScrollingUp = currentScrollTop < this._lastScrollTop;
|
|
411
|
+
this._lastScrollTop = currentScrollTop;
|
|
412
|
+
|
|
381
413
|
const threshold = 50; // pixels from bottom
|
|
382
414
|
const isAtBottom =
|
|
383
415
|
container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
384
416
|
const hasOverflow = container.scrollHeight > container.clientHeight;
|
|
385
|
-
|
|
417
|
+
// Only show button when: user has scrolled, is not at bottom, is scrolling up
|
|
418
|
+
const shouldShow = !isAtBottom && hasOverflow && this._userHasScrolledManually && isScrollingUp;
|
|
386
419
|
|
|
387
420
|
if (shouldShow !== this._scrollButtonVisible) {
|
|
388
421
|
this._scrollButtonVisible = shouldShow;
|
|
@@ -390,8 +423,13 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
390
423
|
}
|
|
391
424
|
};
|
|
392
425
|
|
|
393
|
-
//
|
|
394
|
-
|
|
426
|
+
// Initialize last scroll position
|
|
427
|
+
this._lastScrollTop = container.scrollTop;
|
|
428
|
+
|
|
429
|
+
// Check initial state unless skipped
|
|
430
|
+
if (!options?.skipInitialCheck) {
|
|
431
|
+
checkScrollPosition();
|
|
432
|
+
}
|
|
395
433
|
|
|
396
434
|
// Listen for scroll events with passive listener for performance
|
|
397
435
|
container.addEventListener('scroll', checkScrollPosition, { passive: true });
|