@bbki.ng/bb-msg-history 0.13.0 → 0.14.0
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 +0 -3
- package/dist/component.js +15 -39
- package/package.json +14 -15
- package/src/component.ts +17 -46
package/dist/component.d.ts
CHANGED
|
@@ -5,9 +5,6 @@ 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;
|
|
11
8
|
static get observedAttributes(): string[];
|
|
12
9
|
constructor();
|
|
13
10
|
attributeChangedCallback(name: string): void;
|
package/dist/component.js
CHANGED
|
@@ -13,9 +13,6 @@ 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;
|
|
19
16
|
this.attachShadow({ mode: 'open' });
|
|
20
17
|
}
|
|
21
18
|
attributeChangedCallback(name) {
|
|
@@ -138,22 +135,18 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
138
135
|
}
|
|
139
136
|
// Smooth scroll to bottom (skip in infinite mode)
|
|
140
137
|
if (!this.hasAttribute('infinite')) {
|
|
141
|
-
// Mark this as a programmatic scroll so the scroll handler ignores it
|
|
142
|
-
this._isProgrammaticScroll = true;
|
|
143
138
|
container.scrollTo({
|
|
144
139
|
top: container.scrollHeight,
|
|
145
140
|
behavior: 'smooth',
|
|
146
141
|
});
|
|
147
|
-
// Reset the flag after smooth scroll animation completes (~300ms)
|
|
148
|
-
setTimeout(() => {
|
|
149
|
-
this._isProgrammaticScroll = false;
|
|
150
|
-
}, 300);
|
|
151
142
|
// Hide scroll button since we're scrolling to bottom
|
|
152
|
-
|
|
153
|
-
if (scrollButton && this._scrollButtonVisible) {
|
|
143
|
+
if (this._scrollButtonVisible) {
|
|
154
144
|
this._scrollButtonVisible = false;
|
|
155
|
-
scrollButton.
|
|
156
|
-
|
|
145
|
+
const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
|
|
146
|
+
if (scrollButton) {
|
|
147
|
+
scrollButton.classList.remove('visible');
|
|
148
|
+
}
|
|
149
|
+
// Dispatch hide event (always, regardless of button visibility)
|
|
157
150
|
this.dispatchEvent(new CustomEvent('bb-scrollbuttonhide', {
|
|
158
151
|
bubbles: true,
|
|
159
152
|
composed: true,
|
|
@@ -303,18 +296,10 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
303
296
|
_setupAfterRender() {
|
|
304
297
|
requestAnimationFrame(() => {
|
|
305
298
|
const container = this.shadowRoot.querySelector('.history');
|
|
306
|
-
const
|
|
307
|
-
const scrollButton = hideScrollButton
|
|
308
|
-
? null
|
|
309
|
-
: this.shadowRoot.querySelector('.scroll-to-bottom');
|
|
299
|
+
const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
|
|
310
300
|
const isInfinite = this.hasAttribute('infinite');
|
|
311
|
-
if (container && !isInfinite
|
|
312
|
-
// Mark as programmatic scroll to prevent triggering user scroll detection
|
|
313
|
-
this._isProgrammaticScroll = true;
|
|
301
|
+
if (container && !isInfinite) {
|
|
314
302
|
container.scrollTop = container.scrollHeight;
|
|
315
|
-
requestAnimationFrame(() => {
|
|
316
|
-
this._isProgrammaticScroll = false;
|
|
317
|
-
});
|
|
318
303
|
this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
|
|
319
304
|
}
|
|
320
305
|
if (scrollButton && !isInfinite) {
|
|
@@ -350,25 +335,18 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
350
335
|
}
|
|
351
336
|
_setupScrollTracking(container, button, options) {
|
|
352
337
|
const checkScrollPosition = () => {
|
|
353
|
-
// Ignore programmatic scrolls - they don't indicate user intent
|
|
354
|
-
if (this._isProgrammaticScroll)
|
|
355
|
-
return;
|
|
356
|
-
// Mark that user has manually scrolled
|
|
357
|
-
if (!this._userHasScrolledManually) {
|
|
358
|
-
this._userHasScrolledManually = true;
|
|
359
|
-
}
|
|
360
|
-
const currentScrollTop = container.scrollTop;
|
|
361
|
-
const isScrollingUp = currentScrollTop < this._lastScrollTop;
|
|
362
|
-
this._lastScrollTop = currentScrollTop;
|
|
363
338
|
const threshold = 50; // pixels from bottom
|
|
364
339
|
const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
365
340
|
const hasOverflow = container.scrollHeight > container.clientHeight;
|
|
366
|
-
//
|
|
367
|
-
const shouldShow = !isAtBottom && hasOverflow
|
|
341
|
+
// Show button when not at bottom and content has overflow
|
|
342
|
+
const shouldShow = !isAtBottom && hasOverflow;
|
|
368
343
|
if (shouldShow !== this._scrollButtonVisible) {
|
|
369
344
|
this._scrollButtonVisible = shouldShow;
|
|
370
|
-
|
|
371
|
-
|
|
345
|
+
// Only toggle button visibility if button exists
|
|
346
|
+
if (button) {
|
|
347
|
+
button.classList.toggle('visible', shouldShow);
|
|
348
|
+
}
|
|
349
|
+
// Dispatch custom event (always, regardless of button visibility)
|
|
372
350
|
this.dispatchEvent(new CustomEvent(shouldShow ? 'bb-scrollbuttonshow' : 'bb-scrollbuttonhide', {
|
|
373
351
|
bubbles: true,
|
|
374
352
|
composed: true,
|
|
@@ -376,8 +354,6 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
376
354
|
}));
|
|
377
355
|
}
|
|
378
356
|
};
|
|
379
|
-
// Initialize last scroll position
|
|
380
|
-
this._lastScrollTop = container.scrollTop;
|
|
381
357
|
// Check initial state unless skipped
|
|
382
358
|
if (!options?.skipInitialCheck) {
|
|
383
359
|
checkScrollPosition();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbki.ng/bb-msg-history",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "A chat-style message history web component",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,19 +23,6 @@
|
|
|
23
23
|
"dist",
|
|
24
24
|
"src"
|
|
25
25
|
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"start": "tsc -w",
|
|
28
|
-
"preview": "python3 -m http.server 8000",
|
|
29
|
-
"prepare": "tsc && cp dist/index.js dist/index.dev.js && terser dist/index.js --compress --mangle --source-map -o dist/index.js",
|
|
30
|
-
"build": "tsc && cp dist/index.js dist/index.dev.js && terser dist/index.js --compress --mangle --source-map -o dist/index.js",
|
|
31
|
-
"lint": "eslint src/**/*.ts",
|
|
32
|
-
"lint:fix": "eslint src/**/*.ts --fix",
|
|
33
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
34
|
-
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
35
|
-
"test": "vitest run",
|
|
36
|
-
"test:watch": "vitest",
|
|
37
|
-
"release": "release-it"
|
|
38
|
-
},
|
|
39
26
|
"lint-staged": {
|
|
40
27
|
"*.ts": [
|
|
41
28
|
"eslint --fix",
|
|
@@ -60,5 +47,17 @@
|
|
|
60
47
|
"terser": "^5.46.0",
|
|
61
48
|
"typescript": "^5.9.3",
|
|
62
49
|
"vitest": "^3.2.4"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"start": "tsc -w",
|
|
53
|
+
"preview": "python3 -m http.server 8000",
|
|
54
|
+
"build": "tsc && cp dist/index.js dist/index.dev.js && terser dist/index.js --compress --mangle --source-map -o dist/index.js",
|
|
55
|
+
"lint": "eslint src/**/*.ts",
|
|
56
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
57
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
58
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"release": "release-it"
|
|
63
62
|
}
|
|
64
|
-
}
|
|
63
|
+
}
|
package/src/component.ts
CHANGED
|
@@ -12,9 +12,6 @@ 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;
|
|
18
15
|
|
|
19
16
|
static get observedAttributes() {
|
|
20
17
|
return ['theme', 'loading', 'hide-scroll-bar', 'infinite', 'hide-scroll-button'];
|
|
@@ -176,26 +173,20 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
176
173
|
|
|
177
174
|
// Smooth scroll to bottom (skip in infinite mode)
|
|
178
175
|
if (!this.hasAttribute('infinite')) {
|
|
179
|
-
// Mark this as a programmatic scroll so the scroll handler ignores it
|
|
180
|
-
this._isProgrammaticScroll = true;
|
|
181
|
-
|
|
182
176
|
container.scrollTo({
|
|
183
177
|
top: container.scrollHeight,
|
|
184
178
|
behavior: 'smooth',
|
|
185
179
|
});
|
|
186
180
|
|
|
187
|
-
// Reset the flag after smooth scroll animation completes (~300ms)
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
this._isProgrammaticScroll = false;
|
|
190
|
-
}, 300);
|
|
191
|
-
|
|
192
181
|
// Hide scroll button since we're scrolling to bottom
|
|
193
|
-
|
|
194
|
-
if (scrollButton && this._scrollButtonVisible) {
|
|
182
|
+
if (this._scrollButtonVisible) {
|
|
195
183
|
this._scrollButtonVisible = false;
|
|
196
|
-
scrollButton.
|
|
184
|
+
const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement | null;
|
|
185
|
+
if (scrollButton) {
|
|
186
|
+
scrollButton.classList.remove('visible');
|
|
187
|
+
}
|
|
197
188
|
|
|
198
|
-
// Dispatch hide event
|
|
189
|
+
// Dispatch hide event (always, regardless of button visibility)
|
|
199
190
|
this.dispatchEvent(
|
|
200
191
|
new CustomEvent('bb-scrollbuttonhide', {
|
|
201
192
|
bubbles: true,
|
|
@@ -380,20 +371,12 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
380
371
|
private _setupAfterRender(): void {
|
|
381
372
|
requestAnimationFrame(() => {
|
|
382
373
|
const container = this.shadowRoot!.querySelector('.history') as HTMLElement;
|
|
383
|
-
const
|
|
384
|
-
const scrollButton = hideScrollButton
|
|
385
|
-
? null
|
|
386
|
-
: (this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement);
|
|
374
|
+
const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement | null;
|
|
387
375
|
const isInfinite = this.hasAttribute('infinite');
|
|
388
376
|
|
|
389
|
-
if (container && !isInfinite
|
|
390
|
-
// Mark as programmatic scroll to prevent triggering user scroll detection
|
|
391
|
-
this._isProgrammaticScroll = true;
|
|
377
|
+
if (container && !isInfinite) {
|
|
392
378
|
container.scrollTop = container.scrollHeight;
|
|
393
|
-
|
|
394
|
-
this._isProgrammaticScroll = false;
|
|
395
|
-
});
|
|
396
|
-
this._setupScrollTracking(container, scrollButton!, { skipInitialCheck: true });
|
|
379
|
+
this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
|
|
397
380
|
}
|
|
398
381
|
|
|
399
382
|
if (scrollButton && !isInfinite) {
|
|
@@ -432,34 +415,25 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
432
415
|
|
|
433
416
|
private _setupScrollTracking(
|
|
434
417
|
container: HTMLElement,
|
|
435
|
-
button: HTMLButtonElement,
|
|
418
|
+
button: HTMLButtonElement | null,
|
|
436
419
|
options?: { skipInitialCheck?: boolean }
|
|
437
420
|
): void {
|
|
438
421
|
const checkScrollPosition = () => {
|
|
439
|
-
// Ignore programmatic scrolls - they don't indicate user intent
|
|
440
|
-
if (this._isProgrammaticScroll) return;
|
|
441
|
-
|
|
442
|
-
// Mark that user has manually scrolled
|
|
443
|
-
if (!this._userHasScrolledManually) {
|
|
444
|
-
this._userHasScrolledManually = true;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const currentScrollTop = container.scrollTop;
|
|
448
|
-
const isScrollingUp = currentScrollTop < this._lastScrollTop;
|
|
449
|
-
this._lastScrollTop = currentScrollTop;
|
|
450
|
-
|
|
451
422
|
const threshold = 50; // pixels from bottom
|
|
452
423
|
const isAtBottom =
|
|
453
424
|
container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
454
425
|
const hasOverflow = container.scrollHeight > container.clientHeight;
|
|
455
|
-
//
|
|
456
|
-
const shouldShow = !isAtBottom && hasOverflow
|
|
426
|
+
// Show button when not at bottom and content has overflow
|
|
427
|
+
const shouldShow = !isAtBottom && hasOverflow;
|
|
457
428
|
|
|
458
429
|
if (shouldShow !== this._scrollButtonVisible) {
|
|
459
430
|
this._scrollButtonVisible = shouldShow;
|
|
460
|
-
|
|
431
|
+
// Only toggle button visibility if button exists
|
|
432
|
+
if (button) {
|
|
433
|
+
button.classList.toggle('visible', shouldShow);
|
|
434
|
+
}
|
|
461
435
|
|
|
462
|
-
// Dispatch custom event
|
|
436
|
+
// Dispatch custom event (always, regardless of button visibility)
|
|
463
437
|
this.dispatchEvent(
|
|
464
438
|
new CustomEvent(shouldShow ? 'bb-scrollbuttonshow' : 'bb-scrollbuttonhide', {
|
|
465
439
|
bubbles: true,
|
|
@@ -470,9 +444,6 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
470
444
|
}
|
|
471
445
|
};
|
|
472
446
|
|
|
473
|
-
// Initialize last scroll position
|
|
474
|
-
this._lastScrollTop = container.scrollTop;
|
|
475
|
-
|
|
476
447
|
// Check initial state unless skipped
|
|
477
448
|
if (!options?.skipInitialCheck) {
|
|
478
449
|
checkScrollPosition();
|