@bbki.ng/bb-msg-history 0.10.0 → 0.11.1

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/README.md CHANGED
@@ -130,6 +130,29 @@ You can also use the HTML attribute:
130
130
  </bb-msg-history>
131
131
  ```
132
132
 
133
+ ### `infinite` Attribute
134
+
135
+ Remove the height constraint and disable scrolling on the component. The container expands to fit all messages.
136
+
137
+ Use this when:
138
+ - The parent container handles scrolling
139
+ - You want to display an entire conversation without height limits
140
+ - You need the component to be part of a larger scrollable area
141
+
142
+ ```html
143
+ <bb-msg-history infinite>
144
+ alice: First message
145
+ bob: Second message
146
+ alice: Third message
147
+ <!-- Container keeps expanding to fit all messages -->
148
+ </bb-msg-history>
149
+ ```
150
+
151
+ In infinite mode:
152
+ - No `max-height` constraint is applied
153
+ - No scrollbar appears on the component
154
+ - The scroll-to-bottom button is hidden (not needed)
155
+
133
156
  ## Customization
134
157
 
135
158
  ### CSS Custom Properties
@@ -175,6 +198,7 @@ define('my-chat-history');
175
198
  - `prefers-reduced-motion` support
176
199
  - Reactive: automatically re-renders when content changes
177
200
  - Customizable max-height via `--bb-max-height` CSS custom property
201
+ - **`infinite` attribute** — remove height constraints for parent-controlled scrolling
178
202
  - Graceful degradation to `<pre>` when Custom Elements are unsupported
179
203
 
180
204
  ## Examples
package/dist/component.js CHANGED
@@ -6,7 +6,7 @@ import { buildMessageRowHtml, setupTooltipForElement } from './utils/message-bui
6
6
  import { buildScrollButtonHtml } from './utils/scroll-button.js';
7
7
  export class BBMsgHistory extends HTMLElement {
8
8
  static get observedAttributes() {
9
- return ['theme', 'loading', 'hide-scroll-bar'];
9
+ return ['theme', 'loading', 'hide-scroll-bar', 'infinite'];
10
10
  }
11
11
  constructor() {
12
12
  super();
@@ -16,7 +16,7 @@ export class BBMsgHistory extends HTMLElement {
16
16
  this.attachShadow({ mode: 'open' });
17
17
  }
18
18
  attributeChangedCallback(name) {
19
- if (name === 'theme' || name === 'loading' || name === 'hide-scroll-bar') {
19
+ if (name === 'theme' || name === 'loading' || name === 'hide-scroll-bar' || name === 'infinite') {
20
20
  this.render();
21
21
  }
22
22
  }
@@ -113,16 +113,18 @@ export class BBMsgHistory extends HTMLElement {
113
113
  if (newWrapper) {
114
114
  setupTooltipForElement(newWrapper);
115
115
  }
116
- // Smooth scroll to bottom
117
- container.scrollTo({
118
- top: container.scrollHeight,
119
- behavior: 'smooth',
120
- });
121
- // Hide scroll button since we're scrolling to bottom
122
- const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
123
- if (scrollButton && this._scrollButtonVisible) {
124
- this._scrollButtonVisible = false;
125
- scrollButton.classList.remove('visible');
116
+ // Smooth scroll to bottom (skip in infinite mode)
117
+ if (!this.hasAttribute('infinite')) {
118
+ container.scrollTo({
119
+ top: container.scrollHeight,
120
+ behavior: 'smooth',
121
+ });
122
+ // Hide scroll button since we're scrolling to bottom
123
+ const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
124
+ if (scrollButton && this._scrollButtonVisible) {
125
+ this._scrollButtonVisible = false;
126
+ scrollButton.classList.remove('visible');
127
+ }
126
128
  }
127
129
  }
128
130
  connectedCallback() {
@@ -266,11 +268,12 @@ export class BBMsgHistory extends HTMLElement {
266
268
  requestAnimationFrame(() => {
267
269
  const container = this.shadowRoot.querySelector('.history');
268
270
  const scrollButton = this.shadowRoot.querySelector('.scroll-to-bottom');
269
- if (container) {
271
+ const isInfinite = this.hasAttribute('infinite');
272
+ if (container && !isInfinite) {
270
273
  container.scrollTop = container.scrollHeight;
271
- this._setupScrollTracking(container, scrollButton);
274
+ this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
272
275
  }
273
- if (scrollButton) {
276
+ if (scrollButton && !isInfinite) {
274
277
  scrollButton.addEventListener('click', () => {
275
278
  container?.scrollTo({
276
279
  top: container.scrollHeight,
@@ -301,7 +304,7 @@ export class BBMsgHistory extends HTMLElement {
301
304
  `;
302
305
  }
303
306
  }
304
- _setupScrollTracking(container, button) {
307
+ _setupScrollTracking(container, button, options) {
305
308
  const checkScrollPosition = () => {
306
309
  const threshold = 50; // pixels from bottom
307
310
  const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
@@ -312,8 +315,10 @@ export class BBMsgHistory extends HTMLElement {
312
315
  button.classList.toggle('visible', shouldShow);
313
316
  }
314
317
  };
315
- // Check initial state
316
- checkScrollPosition();
318
+ // Check initial state unless skipped
319
+ if (!options?.skipInitialCheck) {
320
+ checkScrollPosition();
321
+ }
317
322
  // Listen for scroll events with passive listener for performance
318
323
  container.addEventListener('scroll', checkScrollPosition, { passive: true });
319
324
  // Also check on resize
@@ -60,6 +60,17 @@ export const MAIN_STYLES = `
60
60
  display: none; /* Chrome, Safari, Opera */
61
61
  }
62
62
 
63
+ /* Infinite mode - no max height, no scroll */
64
+ :host([infinite]) .history {
65
+ max-height: none;
66
+ overflow-y: visible;
67
+ }
68
+
69
+ /* Hide scroll button in infinite mode */
70
+ :host([infinite]) .scroll-to-bottom {
71
+ display: none;
72
+ }
73
+
63
74
  /* Scroll to bottom button */
64
75
  .scroll-to-bottom {
65
76
  position: absolute;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/bb-msg-history",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "A chat-style message history web component",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/src/component.ts CHANGED
@@ -14,7 +14,7 @@ export class BBMsgHistory extends HTMLElement {
14
14
  private _scrollButtonVisible = false;
15
15
 
16
16
  static get observedAttributes() {
17
- return ['theme', 'loading', 'hide-scroll-bar'];
17
+ return ['theme', 'loading', 'hide-scroll-bar', 'infinite'];
18
18
  }
19
19
 
20
20
  constructor() {
@@ -23,7 +23,7 @@ export class BBMsgHistory extends HTMLElement {
23
23
  }
24
24
 
25
25
  attributeChangedCallback(name: string) {
26
- if (name === 'theme' || name === 'loading' || name === 'hide-scroll-bar') {
26
+ if (name === 'theme' || name === 'loading' || name === 'hide-scroll-bar' || name === 'infinite') {
27
27
  this.render();
28
28
  }
29
29
  }
@@ -147,17 +147,19 @@ export class BBMsgHistory extends HTMLElement {
147
147
  setupTooltipForElement(newWrapper);
148
148
  }
149
149
 
150
- // Smooth scroll to bottom
151
- container.scrollTo({
152
- top: container.scrollHeight,
153
- behavior: 'smooth',
154
- });
150
+ // Smooth scroll to bottom (skip in infinite mode)
151
+ if (!this.hasAttribute('infinite')) {
152
+ container.scrollTo({
153
+ top: container.scrollHeight,
154
+ behavior: 'smooth',
155
+ });
155
156
 
156
- // Hide scroll button since we're scrolling to bottom
157
- const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement;
158
- if (scrollButton && this._scrollButtonVisible) {
159
- this._scrollButtonVisible = false;
160
- scrollButton.classList.remove('visible');
157
+ // Hide scroll button since we're scrolling to bottom
158
+ const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement;
159
+ if (scrollButton && this._scrollButtonVisible) {
160
+ this._scrollButtonVisible = false;
161
+ scrollButton.classList.remove('visible');
162
+ }
161
163
  }
162
164
  }
163
165
 
@@ -333,13 +335,14 @@ export class BBMsgHistory extends HTMLElement {
333
335
  requestAnimationFrame(() => {
334
336
  const container = this.shadowRoot!.querySelector('.history') as HTMLElement;
335
337
  const scrollButton = this.shadowRoot!.querySelector('.scroll-to-bottom') as HTMLButtonElement;
338
+ const isInfinite = this.hasAttribute('infinite');
336
339
 
337
- if (container) {
340
+ if (container && !isInfinite) {
338
341
  container.scrollTop = container.scrollHeight;
339
- this._setupScrollTracking(container, scrollButton);
342
+ this._setupScrollTracking(container, scrollButton, { skipInitialCheck: true });
340
343
  }
341
344
 
342
- if (scrollButton) {
345
+ if (scrollButton && !isInfinite) {
343
346
  scrollButton.addEventListener('click', () => {
344
347
  container?.scrollTo({
345
348
  top: container.scrollHeight,
@@ -373,7 +376,11 @@ export class BBMsgHistory extends HTMLElement {
373
376
  }
374
377
  }
375
378
 
376
- private _setupScrollTracking(container: HTMLElement, button: HTMLButtonElement): void {
379
+ private _setupScrollTracking(
380
+ container: HTMLElement,
381
+ button: HTMLButtonElement,
382
+ options?: { skipInitialCheck?: boolean }
383
+ ): void {
377
384
  const checkScrollPosition = () => {
378
385
  const threshold = 50; // pixels from bottom
379
386
  const isAtBottom =
@@ -387,8 +394,10 @@ export class BBMsgHistory extends HTMLElement {
387
394
  }
388
395
  };
389
396
 
390
- // Check initial state
391
- checkScrollPosition();
397
+ // Check initial state unless skipped
398
+ if (!options?.skipInitialCheck) {
399
+ checkScrollPosition();
400
+ }
392
401
 
393
402
  // Listen for scroll events with passive listener for performance
394
403
  container.addEventListener('scroll', checkScrollPosition, { passive: true });
@@ -61,6 +61,17 @@ export const MAIN_STYLES = `
61
61
  display: none; /* Chrome, Safari, Opera */
62
62
  }
63
63
 
64
+ /* Infinite mode - no max height, no scroll */
65
+ :host([infinite]) .history {
66
+ max-height: none;
67
+ overflow-y: visible;
68
+ }
69
+
70
+ /* Hide scroll button in infinite mode */
71
+ :host([infinite]) .scroll-to-bottom {
72
+ display: none;
73
+ }
74
+
64
75
  /* Scroll to bottom button */
65
76
  .scroll-to-bottom {
66
77
  position: absolute;