@bbki.ng/bb-msg-history 0.6.1 → 0.7.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 +58 -0
- package/dist/component.d.ts +9 -1
- package/dist/component.js +42 -9
- package/dist/const/styles.d.ts +4 -0
- package/dist/const/styles.js +62 -0
- package/package.json +1 -1
- package/src/component.ts +44 -9
- package/src/const/styles.ts +63 -0
package/README.md
CHANGED
|
@@ -101,6 +101,35 @@ Returns `this` for chaining. This is ideal for chat applications where messages
|
|
|
101
101
|
|
|
102
102
|
**Note:** Unlike modifying `textContent` directly, `appendMessage()` scrolls smoothly to the newly added message.
|
|
103
103
|
|
|
104
|
+
### `setLoading(isLoading)`
|
|
105
|
+
|
|
106
|
+
Show or hide a loading animation overlay. Useful when fetching messages from an API.
|
|
107
|
+
|
|
108
|
+
| Parameter | Type | Description |
|
|
109
|
+
|-----------|------|-------------|
|
|
110
|
+
| `isLoading` | `boolean` | `true` to show loading, `false` to hide |
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
const el = document.querySelector('bb-msg-history');
|
|
114
|
+
|
|
115
|
+
// Show loading
|
|
116
|
+
el.setLoading(true);
|
|
117
|
+
|
|
118
|
+
// Fetch messages
|
|
119
|
+
fetchMessages().then(messages => {
|
|
120
|
+
// Hide loading and display messages
|
|
121
|
+
el.setLoading(false);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
You can also use the HTML attribute:
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<bb-msg-history loading>
|
|
129
|
+
alice: Loading previous messages...
|
|
130
|
+
</bb-msg-history>
|
|
131
|
+
```
|
|
132
|
+
|
|
104
133
|
## Customization
|
|
105
134
|
|
|
106
135
|
### CSS Custom Properties
|
|
@@ -138,6 +167,7 @@ define('my-chat-history');
|
|
|
138
167
|
- Consecutive messages from the same author are grouped (avatar hidden)
|
|
139
168
|
- Auto-scroll to the latest message on render
|
|
140
169
|
- **`appendMessage()` API** — programmatically add messages with smooth scroll
|
|
170
|
+
- **`setLoading()` API** — show loading animation while fetching messages
|
|
141
171
|
- Long text word-wrap and overflow handling
|
|
142
172
|
- Empty state when no messages are provided
|
|
143
173
|
- Dark mode support via `prefers-color-scheme`
|
|
@@ -234,6 +264,34 @@ Use `appendMessage()` to add messages programmatically with smooth scrolling:
|
|
|
234
264
|
</script>
|
|
235
265
|
```
|
|
236
266
|
|
|
267
|
+
### Loading state
|
|
268
|
+
|
|
269
|
+
Show a loading animation while fetching messages from an API:
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<bb-msg-history id="chat" loading>
|
|
273
|
+
<!-- Messages will be loaded -->
|
|
274
|
+
</bb-msg-history>
|
|
275
|
+
|
|
276
|
+
<script>
|
|
277
|
+
const el = document.getElementById('chat');
|
|
278
|
+
|
|
279
|
+
// Show loading (already set via HTML attribute above)
|
|
280
|
+
// el.setLoading(true);
|
|
281
|
+
|
|
282
|
+
// Fetch messages from API
|
|
283
|
+
fetch('/api/messages')
|
|
284
|
+
.then(res => res.json())
|
|
285
|
+
.then(messages => {
|
|
286
|
+
// Hide loading and populate messages
|
|
287
|
+
el.setLoading(false);
|
|
288
|
+
messages.forEach(msg => {
|
|
289
|
+
el.appendMessage({ author: msg.author, text: msg.text });
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
</script>
|
|
293
|
+
```
|
|
294
|
+
|
|
237
295
|
### Full page example
|
|
238
296
|
|
|
239
297
|
```html
|
package/dist/component.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare class BBMsgHistory extends HTMLElement {
|
|
|
7
7
|
private _scrollButtonVisible;
|
|
8
8
|
static get observedAttributes(): string[];
|
|
9
9
|
constructor();
|
|
10
|
-
attributeChangedCallback(): void;
|
|
10
|
+
attributeChangedCallback(name: string): void;
|
|
11
11
|
/**
|
|
12
12
|
* Configure an author's avatar, side, and colors.
|
|
13
13
|
* Call before or after rendering — the component re-renders automatically.
|
|
@@ -21,6 +21,14 @@ export declare class BBMsgHistory extends HTMLElement {
|
|
|
21
21
|
* Remove a previously set author config.
|
|
22
22
|
*/
|
|
23
23
|
removeAuthor(name: string): this;
|
|
24
|
+
/**
|
|
25
|
+
* Show or hide the loading overlay.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* el.setLoading(true); // Show loading animation
|
|
29
|
+
* el.setLoading(false); // Hide loading animation
|
|
30
|
+
*/
|
|
31
|
+
setLoading(isLoading: boolean): this;
|
|
24
32
|
/**
|
|
25
33
|
* Append a message to the history.
|
|
26
34
|
* Automatically scrolls to the new message with smooth animation.
|
package/dist/component.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EMPTY_STYLES, LOADING_STYLES, MAIN_STYLES } from './const/styles.js';
|
|
2
2
|
import { parseMessages } from './utils/message-parser.js';
|
|
3
3
|
import { resolveAuthorConfig } from './utils/author-resolver.js';
|
|
4
4
|
import { setupTooltips } from './utils/tooltip.js';
|
|
@@ -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'];
|
|
9
|
+
return ['theme', 'loading'];
|
|
10
10
|
}
|
|
11
11
|
constructor() {
|
|
12
12
|
super();
|
|
@@ -15,8 +15,10 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
15
15
|
this._scrollButtonVisible = false;
|
|
16
16
|
this.attachShadow({ mode: 'open' });
|
|
17
17
|
}
|
|
18
|
-
attributeChangedCallback() {
|
|
19
|
-
|
|
18
|
+
attributeChangedCallback(name) {
|
|
19
|
+
if (name === 'theme' || name === 'loading') {
|
|
20
|
+
this.render();
|
|
21
|
+
}
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Configure an author's avatar, side, and colors.
|
|
@@ -39,6 +41,17 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
39
41
|
this.render();
|
|
40
42
|
return this;
|
|
41
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Show or hide the loading overlay.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* el.setLoading(true); // Show loading animation
|
|
49
|
+
* el.setLoading(false); // Hide loading animation
|
|
50
|
+
*/
|
|
51
|
+
setLoading(isLoading) {
|
|
52
|
+
this.toggleAttribute('loading', isLoading);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
42
55
|
/**
|
|
43
56
|
* Append a message to the history.
|
|
44
57
|
* Automatically scrolls to the new message with smooth animation.
|
|
@@ -191,12 +204,18 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
191
204
|
})
|
|
192
205
|
.join('');
|
|
193
206
|
this._lastAuthor = lastAuthor;
|
|
207
|
+
const loadingOverlay = this.hasAttribute('loading')
|
|
208
|
+
? `<div class="loading-overlay" role="status" aria-label="Loading messages">
|
|
209
|
+
<div class="loading-spinner"></div>
|
|
210
|
+
</div>`
|
|
211
|
+
: '';
|
|
194
212
|
this.shadowRoot.innerHTML = `
|
|
195
|
-
<style>${MAIN_STYLES}</style>
|
|
213
|
+
<style>${MAIN_STYLES}${LOADING_STYLES}</style>
|
|
196
214
|
<div class="history" role="log" aria-live="polite" aria-label="Message history">
|
|
197
215
|
${messagesHtml}
|
|
198
216
|
</div>
|
|
199
217
|
${buildScrollButtonHtml()}
|
|
218
|
+
${loadingOverlay}
|
|
200
219
|
`;
|
|
201
220
|
requestAnimationFrame(() => {
|
|
202
221
|
const container = this.shadowRoot.querySelector('.history');
|
|
@@ -217,10 +236,24 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
217
236
|
});
|
|
218
237
|
}
|
|
219
238
|
_renderEmpty() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
239
|
+
const isLoading = this.hasAttribute('loading');
|
|
240
|
+
if (isLoading) {
|
|
241
|
+
// Show loading overlay with minimum height for better appearance
|
|
242
|
+
this.shadowRoot.innerHTML = `
|
|
243
|
+
<style>${EMPTY_STYLES}${LOADING_STYLES}</style>
|
|
244
|
+
<div style="position: relative; min-height: 120px;">
|
|
245
|
+
<div class="loading-overlay" role="status" aria-label="Loading messages">
|
|
246
|
+
<div class="loading-spinner"></div>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
this.shadowRoot.innerHTML = `
|
|
253
|
+
<style>${EMPTY_STYLES}</style>
|
|
254
|
+
<div class="empty-state">No messages</div>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
224
257
|
}
|
|
225
258
|
_setupScrollTracking(container, button) {
|
|
226
259
|
const checkScrollPosition = () => {
|
package/dist/const/styles.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare const MAIN_STYLES: string;
|
|
|
6
6
|
* Empty state styles
|
|
7
7
|
*/
|
|
8
8
|
export declare const EMPTY_STYLES: string;
|
|
9
|
+
/**
|
|
10
|
+
* Loading overlay styles with elegant spinner
|
|
11
|
+
*/
|
|
12
|
+
export declare const LOADING_STYLES: string;
|
|
9
13
|
/**
|
|
10
14
|
* Fallback styles for when custom elements are not supported
|
|
11
15
|
*/
|
package/dist/const/styles.js
CHANGED
|
@@ -444,6 +444,68 @@ export const EMPTY_STYLES = `
|
|
|
444
444
|
font-family: inherit;
|
|
445
445
|
}
|
|
446
446
|
`;
|
|
447
|
+
/**
|
|
448
|
+
* Loading overlay styles with elegant spinner
|
|
449
|
+
*/
|
|
450
|
+
export const LOADING_STYLES = `
|
|
451
|
+
.loading-overlay {
|
|
452
|
+
position: absolute;
|
|
453
|
+
inset: 0;
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
justify-content: center;
|
|
457
|
+
background: rgba(255, 255, 255, 0.6);
|
|
458
|
+
backdrop-filter: blur(1px);
|
|
459
|
+
z-index: 20;
|
|
460
|
+
border-radius: 0.5rem;
|
|
461
|
+
min-height: 120px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.loading-spinner {
|
|
465
|
+
width: 24px;
|
|
466
|
+
height: 24px;
|
|
467
|
+
border: 2px solid ${THEME.gray[200]};
|
|
468
|
+
border-top-color: ${THEME.gray[500]};
|
|
469
|
+
border-radius: 50%;
|
|
470
|
+
animation: spin 0.8s linear infinite;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
@keyframes spin {
|
|
474
|
+
to {
|
|
475
|
+
transform: rotate(360deg);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Dark mode support */
|
|
480
|
+
:host([theme="dark"]) .loading-overlay {
|
|
481
|
+
background: rgba(17, 24, 39, 0.6);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
:host([theme="dark"]) .loading-spinner {
|
|
485
|
+
border-color: ${THEME.gray[700]};
|
|
486
|
+
border-top-color: ${THEME.gray[400]};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* System dark mode preference */
|
|
490
|
+
@media (prefers-color-scheme: dark) {
|
|
491
|
+
:host .loading-overlay {
|
|
492
|
+
background: rgba(17, 24, 39, 0.6);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
:host .loading-spinner {
|
|
496
|
+
border-color: ${THEME.gray[700]};
|
|
497
|
+
border-top-color: ${THEME.gray[400]};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Reduced motion */
|
|
502
|
+
@media (prefers-reduced-motion: reduce) {
|
|
503
|
+
.loading-spinner {
|
|
504
|
+
animation-duration: 1.5s;
|
|
505
|
+
opacity: 0.8;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
`;
|
|
447
509
|
/**
|
|
448
510
|
* Fallback styles for when custom elements are not supported
|
|
449
511
|
*/
|
package/package.json
CHANGED
package/src/component.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthorOptions, Message } from './types/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { EMPTY_STYLES, LOADING_STYLES, MAIN_STYLES } from './const/styles.js';
|
|
3
3
|
import { parseMessages } from './utils/message-parser.js';
|
|
4
4
|
import { resolveAuthorConfig } from './utils/author-resolver.js';
|
|
5
5
|
import { setupTooltips } from './utils/tooltip.js';
|
|
@@ -14,7 +14,7 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
14
14
|
private _scrollButtonVisible = false;
|
|
15
15
|
|
|
16
16
|
static get observedAttributes() {
|
|
17
|
-
return ['theme'];
|
|
17
|
+
return ['theme', 'loading'];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
constructor() {
|
|
@@ -22,8 +22,10 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
22
22
|
this.attachShadow({ mode: 'open' });
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
attributeChangedCallback() {
|
|
26
|
-
|
|
25
|
+
attributeChangedCallback(name: string) {
|
|
26
|
+
if (name === 'theme' || name === 'loading') {
|
|
27
|
+
this.render();
|
|
28
|
+
}
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -49,6 +51,18 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
49
51
|
return this;
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Show or hide the loading overlay.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* el.setLoading(true); // Show loading animation
|
|
59
|
+
* el.setLoading(false); // Hide loading animation
|
|
60
|
+
*/
|
|
61
|
+
setLoading(isLoading: boolean): this {
|
|
62
|
+
this.toggleAttribute('loading', isLoading);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
/**
|
|
53
67
|
* Append a message to the history.
|
|
54
68
|
* Automatically scrolls to the new message with smooth animation.
|
|
@@ -246,12 +260,19 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
246
260
|
|
|
247
261
|
this._lastAuthor = lastAuthor;
|
|
248
262
|
|
|
263
|
+
const loadingOverlay = this.hasAttribute('loading')
|
|
264
|
+
? `<div class="loading-overlay" role="status" aria-label="Loading messages">
|
|
265
|
+
<div class="loading-spinner"></div>
|
|
266
|
+
</div>`
|
|
267
|
+
: '';
|
|
268
|
+
|
|
249
269
|
this.shadowRoot!.innerHTML = `
|
|
250
|
-
<style>${MAIN_STYLES}</style>
|
|
270
|
+
<style>${MAIN_STYLES}${LOADING_STYLES}</style>
|
|
251
271
|
<div class="history" role="log" aria-live="polite" aria-label="Message history">
|
|
252
272
|
${messagesHtml}
|
|
253
273
|
</div>
|
|
254
274
|
${buildScrollButtonHtml()}
|
|
275
|
+
${loadingOverlay}
|
|
255
276
|
`;
|
|
256
277
|
|
|
257
278
|
requestAnimationFrame(() => {
|
|
@@ -277,10 +298,24 @@ export class BBMsgHistory extends HTMLElement {
|
|
|
277
298
|
}
|
|
278
299
|
|
|
279
300
|
private _renderEmpty() {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
301
|
+
const isLoading = this.hasAttribute('loading');
|
|
302
|
+
|
|
303
|
+
if (isLoading) {
|
|
304
|
+
// Show loading overlay with minimum height for better appearance
|
|
305
|
+
this.shadowRoot!.innerHTML = `
|
|
306
|
+
<style>${EMPTY_STYLES}${LOADING_STYLES}</style>
|
|
307
|
+
<div style="position: relative; min-height: 120px;">
|
|
308
|
+
<div class="loading-overlay" role="status" aria-label="Loading messages">
|
|
309
|
+
<div class="loading-spinner"></div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
`;
|
|
313
|
+
} else {
|
|
314
|
+
this.shadowRoot!.innerHTML = `
|
|
315
|
+
<style>${EMPTY_STYLES}</style>
|
|
316
|
+
<div class="empty-state">No messages</div>
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
284
319
|
}
|
|
285
320
|
|
|
286
321
|
private _setupScrollTracking(container: HTMLElement, button: HTMLButtonElement): void {
|
package/src/const/styles.ts
CHANGED
|
@@ -447,6 +447,69 @@ export const EMPTY_STYLES = `
|
|
|
447
447
|
}
|
|
448
448
|
`;
|
|
449
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Loading overlay styles with elegant spinner
|
|
452
|
+
*/
|
|
453
|
+
export const LOADING_STYLES = `
|
|
454
|
+
.loading-overlay {
|
|
455
|
+
position: absolute;
|
|
456
|
+
inset: 0;
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
justify-content: center;
|
|
460
|
+
background: rgba(255, 255, 255, 0.6);
|
|
461
|
+
backdrop-filter: blur(1px);
|
|
462
|
+
z-index: 20;
|
|
463
|
+
border-radius: 0.5rem;
|
|
464
|
+
min-height: 120px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.loading-spinner {
|
|
468
|
+
width: 24px;
|
|
469
|
+
height: 24px;
|
|
470
|
+
border: 2px solid ${THEME.gray[200]};
|
|
471
|
+
border-top-color: ${THEME.gray[500]};
|
|
472
|
+
border-radius: 50%;
|
|
473
|
+
animation: spin 0.8s linear infinite;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@keyframes spin {
|
|
477
|
+
to {
|
|
478
|
+
transform: rotate(360deg);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Dark mode support */
|
|
483
|
+
:host([theme="dark"]) .loading-overlay {
|
|
484
|
+
background: rgba(17, 24, 39, 0.6);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
:host([theme="dark"]) .loading-spinner {
|
|
488
|
+
border-color: ${THEME.gray[700]};
|
|
489
|
+
border-top-color: ${THEME.gray[400]};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* System dark mode preference */
|
|
493
|
+
@media (prefers-color-scheme: dark) {
|
|
494
|
+
:host .loading-overlay {
|
|
495
|
+
background: rgba(17, 24, 39, 0.6);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
:host .loading-spinner {
|
|
499
|
+
border-color: ${THEME.gray[700]};
|
|
500
|
+
border-top-color: ${THEME.gray[400]};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* Reduced motion */
|
|
505
|
+
@media (prefers-reduced-motion: reduce) {
|
|
506
|
+
.loading-spinner {
|
|
507
|
+
animation-duration: 1.5s;
|
|
508
|
+
opacity: 0.8;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
`;
|
|
512
|
+
|
|
450
513
|
/**
|
|
451
514
|
* Fallback styles for when custom elements are not supported
|
|
452
515
|
*/
|