@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.
@@ -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
- const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
153
- if (scrollButton && this._scrollButtonVisible) {
143
+ if (this._scrollButtonVisible) {
154
144
  this._scrollButtonVisible = false;
155
- scrollButton.classList.remove('visible');
156
- // Dispatch hide event
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 hideScrollButton = this.hasAttribute('hide-scroll-button');
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 && !hideScrollButton) {
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
- // Only show button when: user has scrolled, is not at bottom, is scrolling up
367
- const shouldShow = !isAtBottom && hasOverflow && this._userHasScrolledManually && isScrollingUp;
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
- button.classList.toggle('visible', shouldShow);
371
- // Dispatch custom event
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.13.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
- const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement;
194
- if (scrollButton && this._scrollButtonVisible) {
182
+ if (this._scrollButtonVisible) {
195
183
  this._scrollButtonVisible = false;
196
- scrollButton.classList.remove('visible');
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 hideScrollButton = this.hasAttribute('hide-scroll-button');
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 && !hideScrollButton) {
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
- requestAnimationFrame(() => {
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
- // Only show button when: user has scrolled, is not at bottom, is scrolling up
456
- const shouldShow = !isAtBottom && hasOverflow && this._userHasScrolledManually && isScrollingUp;
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
- button.classList.toggle('visible', shouldShow);
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();