@bbki.ng/bb-msg-history 0.9.0 → 0.11.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/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
274
  this._setupScrollTracking(container, scrollButton);
272
275
  }
273
- if (scrollButton) {
276
+ if (scrollButton && !isInfinite) {
274
277
  scrollButton.addEventListener('click', () => {
275
278
  container?.scrollTo({
276
279
  top: container.scrollHeight,
@@ -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;
@@ -68,7 +79,7 @@ export const MAIN_STYLES = `
68
79
  width: 36px;
69
80
  height: 36px;
70
81
  border-radius: 50%;
71
- background: transparent;
82
+ background: #ffffff;
72
83
  border: none;
73
84
  color: ${THEME.gray[500]};
74
85
  cursor: pointer;
@@ -77,8 +88,8 @@ export const MAIN_STYLES = `
77
88
  justify-content: center;
78
89
  opacity: 0;
79
90
  visibility: hidden;
80
- transform: translateX(-50%) translateY(10px) scale(0.9);
81
- transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease;
91
+ transform: translateX(-50%) translateY(10px) scale(0);
92
+ transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
82
93
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
83
94
  z-index: 10;
84
95
  }
@@ -91,12 +102,12 @@ export const MAIN_STYLES = `
91
102
 
92
103
  .scroll-to-bottom:hover {
93
104
  color: ${THEME.gray[700]};
94
- transform: translateX(-50%) translateY(-2px);
105
+ transform: translateX(-50%) translateY(-2px) scale(1.05);
95
106
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
96
107
  }
97
108
 
98
109
  .scroll-to-bottom:active {
99
- transform: translateX(-50%) translateY(0) scale(0.95);
110
+ transform: translateX(-50%) translateY(-1px) scale(0.95);
100
111
  }
101
112
 
102
113
  .scroll-to-bottom svg {
@@ -425,19 +436,19 @@ export const MAIN_STYLES = `
425
436
 
426
437
  .scroll-to-bottom {
427
438
  transition: opacity 0.15s ease, visibility 0.15s ease;
428
- transform: translateX(-50%);
439
+ transform: translateX(-50%) translateY(10px) scale(0);
429
440
  }
430
441
 
431
442
  .scroll-to-bottom.visible {
432
- transform: translateX(-50%);
443
+ transform: translateX(-50%) translateY(0) scale(1);
433
444
  }
434
445
 
435
446
  .scroll-to-bottom:hover {
436
- transform: translateX(-50%);
447
+ transform: translateX(-50%) translateY(-2px) scale(1);
437
448
  }
438
449
 
439
450
  .scroll-to-bottom:active {
440
- transform: translateX(-50%);
451
+ transform: translateX(-50%) translateY(0) scale(0.95);
441
452
  }
442
453
  }
443
454
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbki.ng/bb-msg-history",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
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
342
  this._setupScrollTracking(container, scrollButton);
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,
@@ -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;
@@ -69,7 +80,7 @@ export const MAIN_STYLES = `
69
80
  width: 36px;
70
81
  height: 36px;
71
82
  border-radius: 50%;
72
- background: transparent;
83
+ background: #ffffff;
73
84
  border: none;
74
85
  color: ${THEME.gray[500]};
75
86
  cursor: pointer;
@@ -78,8 +89,8 @@ export const MAIN_STYLES = `
78
89
  justify-content: center;
79
90
  opacity: 0;
80
91
  visibility: hidden;
81
- transform: translateX(-50%) translateY(10px) scale(0.9);
82
- transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease;
92
+ transform: translateX(-50%) translateY(10px) scale(0);
93
+ transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
83
94
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
84
95
  z-index: 10;
85
96
  }
@@ -92,12 +103,12 @@ export const MAIN_STYLES = `
92
103
 
93
104
  .scroll-to-bottom:hover {
94
105
  color: ${THEME.gray[700]};
95
- transform: translateX(-50%) translateY(-2px);
106
+ transform: translateX(-50%) translateY(-2px) scale(1.05);
96
107
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
97
108
  }
98
109
 
99
110
  .scroll-to-bottom:active {
100
- transform: translateX(-50%) translateY(0) scale(0.95);
111
+ transform: translateX(-50%) translateY(-1px) scale(0.95);
101
112
  }
102
113
 
103
114
  .scroll-to-bottom svg {
@@ -426,19 +437,19 @@ export const MAIN_STYLES = `
426
437
 
427
438
  .scroll-to-bottom {
428
439
  transition: opacity 0.15s ease, visibility 0.15s ease;
429
- transform: translateX(-50%);
440
+ transform: translateX(-50%) translateY(10px) scale(0);
430
441
  }
431
442
 
432
443
  .scroll-to-bottom.visible {
433
- transform: translateX(-50%);
444
+ transform: translateX(-50%) translateY(0) scale(1);
434
445
  }
435
446
 
436
447
  .scroll-to-bottom:hover {
437
- transform: translateX(-50%);
448
+ transform: translateX(-50%) translateY(-2px) scale(1);
438
449
  }
439
450
 
440
451
  .scroll-to-bottom:active {
441
- transform: translateX(-50%);
452
+ transform: translateX(-50%) translateY(0) scale(0.95);
442
453
  }
443
454
  }
444
455
  `;