@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 +24 -0
- package/dist/component.js +23 -18
- package/dist/const/styles.js +11 -0
- package/package.json +1 -1
- package/src/component.ts +27 -18
- package/src/const/styles.ts +11 -0
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this._scrollButtonVisible
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/const/styles.js
CHANGED
|
@@ -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
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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(
|
|
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
|
-
|
|
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 });
|
package/src/const/styles.ts
CHANGED
|
@@ -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;
|