@bbki.ng/bb-msg-history 0.14.1 → 2.0.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.
Files changed (54) hide show
  1. package/dist/component.d.ts +6 -18
  2. package/dist/component.js +43 -275
  3. package/dist/components/bb-custom-avatar.d.ts +20 -0
  4. package/dist/components/bb-custom-avatar.js +145 -0
  5. package/dist/components/bb-letter-avatar.d.ts +14 -0
  6. package/dist/components/bb-letter-avatar.js +61 -0
  7. package/dist/components/bb-loading-overlay.d.ts +14 -0
  8. package/dist/components/bb-loading-overlay.js +89 -0
  9. package/dist/components/bb-message-bubble.d.ts +19 -0
  10. package/dist/components/bb-message-bubble.js +116 -0
  11. package/dist/components/bb-message.d.ts +27 -0
  12. package/dist/components/bb-message.js +174 -0
  13. package/dist/components/bb-msg-history.d.ts +111 -0
  14. package/dist/components/bb-msg-history.js +473 -0
  15. package/dist/components/bb-scroll-button.d.ts +16 -0
  16. package/dist/components/bb-scroll-button.js +161 -0
  17. package/dist/components/bb-timestamp.d.ts +15 -0
  18. package/dist/components/bb-timestamp.js +59 -0
  19. package/dist/components/index.d.ts +7 -0
  20. package/dist/components/index.js +7 -0
  21. package/dist/const/authors.js +1 -1
  22. package/dist/const/styles.js +0 -33
  23. package/dist/contexts/author-context.d.ts +8 -0
  24. package/dist/contexts/author-context.js +6 -0
  25. package/dist/controllers/scroll-controller.d.ts +52 -0
  26. package/dist/controllers/scroll-controller.js +138 -0
  27. package/dist/core/message-processor.d.ts +56 -0
  28. package/dist/core/message-processor.js +85 -0
  29. package/dist/core/renderer.d.ts +87 -0
  30. package/dist/core/renderer.js +196 -0
  31. package/dist/core/scroll-manager.d.ts +54 -0
  32. package/dist/core/scroll-manager.js +119 -0
  33. package/dist/parsers/base.d.ts +21 -0
  34. package/dist/parsers/base.js +1 -0
  35. package/dist/parsers/default-parser.d.ts +10 -0
  36. package/dist/parsers/default-parser.js +40 -0
  37. package/dist/parsers/index.d.ts +2 -0
  38. package/dist/parsers/index.js +1 -0
  39. package/dist/utils/event-tracker.d.ts +23 -0
  40. package/dist/utils/event-tracker.js +33 -0
  41. package/dist/utils/message-builder.d.ts +0 -4
  42. package/dist/utils/message-builder.js +0 -15
  43. package/dist/utils/tooltip.d.ts +11 -2
  44. package/dist/utils/tooltip.js +56 -13
  45. package/package.json +1 -1
  46. package/src/component.ts +56 -338
  47. package/src/const/authors.ts +3 -2
  48. package/src/const/styles.ts +0 -33
  49. package/src/core/message-processor.ts +120 -0
  50. package/src/core/renderer.ts +276 -0
  51. package/src/core/scroll-manager.ts +148 -0
  52. package/src/utils/event-tracker.ts +38 -0
  53. package/src/utils/message-builder.ts +0 -15
  54. package/src/utils/tooltip.ts +0 -16
@@ -0,0 +1,14 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Loading overlay component - displays spinner overlay
4
+ */
5
+ export declare class BBLoadingOverlay extends LitElement {
6
+ static styles: import("lit").CSSResult;
7
+ visible: boolean;
8
+ render(): import("lit").TemplateResult<1>;
9
+ }
10
+ declare global {
11
+ interface HTMLElementTagNameMap {
12
+ 'bb-loading-overlay': BBLoadingOverlay;
13
+ }
14
+ }
@@ -0,0 +1,89 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, html, css, unsafeCSS } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+ import { THEME } from '../const/theme.js';
10
+ /**
11
+ * Loading overlay component - displays spinner overlay
12
+ */
13
+ let BBLoadingOverlay = class BBLoadingOverlay extends LitElement {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.visible = false;
17
+ }
18
+ static { this.styles = css `
19
+ :host {
20
+ display: block;
21
+ position: absolute;
22
+ inset: 0;
23
+ z-index: 20;
24
+ }
25
+
26
+ .overlay {
27
+ position: absolute;
28
+ inset: 0;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ background: rgba(255, 255, 255, 0.6);
33
+ backdrop-filter: blur(1px);
34
+ border-radius: 0.5rem;
35
+ min-height: 120px;
36
+ }
37
+
38
+ .spinner {
39
+ width: 24px;
40
+ height: 24px;
41
+ border: 2px solid ${unsafeCSS(THEME.gray[200])};
42
+ border-top-color: ${unsafeCSS(THEME.gray[500])};
43
+ border-radius: 50%;
44
+ animation: spin 0.8s linear infinite;
45
+ }
46
+
47
+ @keyframes spin {
48
+ to {
49
+ transform: rotate(360deg);
50
+ }
51
+ }
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .spinner {
55
+ animation-duration: 1.5s;
56
+ opacity: 0.8;
57
+ }
58
+ }
59
+
60
+ /* Dark mode */
61
+ @media (prefers-color-scheme: dark) {
62
+ .overlay {
63
+ background: rgba(17, 24, 39, 0.6);
64
+ }
65
+
66
+ .spinner {
67
+ border-color: ${unsafeCSS(THEME.gray[700])};
68
+ border-top-color: ${unsafeCSS(THEME.gray[400])};
69
+ }
70
+ }
71
+ `; }
72
+ render() {
73
+ if (!this.visible) {
74
+ return html ``;
75
+ }
76
+ return html `
77
+ <div class="overlay" role="status" aria-label="Loading messages">
78
+ <div class="spinner"></div>
79
+ </div>
80
+ `;
81
+ }
82
+ };
83
+ __decorate([
84
+ property({ type: Boolean, reflect: true })
85
+ ], BBLoadingOverlay.prototype, "visible", void 0);
86
+ BBLoadingOverlay = __decorate([
87
+ customElement('bb-loading-overlay')
88
+ ], BBLoadingOverlay);
89
+ export { BBLoadingOverlay };
@@ -0,0 +1,19 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Message bubble component - displays message text with theming
4
+ */
5
+ export declare class BBMessageBubble extends LitElement {
6
+ static styles: import("lit").CSSResult;
7
+ text: string;
8
+ side: 'left' | 'right';
9
+ subsequent: boolean;
10
+ customBg: string;
11
+ customColor: string;
12
+ private _getInlineStyles;
13
+ render(): import("lit").TemplateResult<1>;
14
+ }
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'bb-message-bubble': BBMessageBubble;
18
+ }
19
+ }
@@ -0,0 +1,116 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, html, css, unsafeCSS } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+ import { THEME } from '../const/theme.js';
10
+ /**
11
+ * Message bubble component - displays message text with theming
12
+ */
13
+ let BBMessageBubble = class BBMessageBubble extends LitElement {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.text = '';
17
+ this.side = 'left';
18
+ this.subsequent = false;
19
+ this.customBg = '';
20
+ this.customColor = '';
21
+ }
22
+ static { this.styles = css `
23
+ :host {
24
+ display: block;
25
+ --bubble-bg: var(--bb-bubble-bg, ${unsafeCSS(THEME.gray[50])});
26
+ --bubble-color: var(--bb-bubble-color, ${unsafeCSS(THEME.gray[900])});
27
+ --bubble-radius: 1rem;
28
+ }
29
+
30
+ :host([side='right']) {
31
+ --bubble-bg: var(--bb-bubble-right-bg, ${unsafeCSS(THEME.gray[200])});
32
+ }
33
+
34
+ .bubble {
35
+ padding: 0.625rem 0.875rem;
36
+ font-size: 0.9375rem;
37
+ line-height: 1.5;
38
+ word-wrap: break-word;
39
+ overflow-wrap: anywhere;
40
+ word-break: break-word;
41
+ border-radius: var(--bubble-radius);
42
+ background-color: var(--bubble-bg);
43
+ color: var(--bubble-color);
44
+ }
45
+
46
+ /* Left side bubble - squared bottom left */
47
+ :host([side='left']:not([subsequent])) .bubble {
48
+ border-bottom-left-radius: 0.25rem;
49
+ }
50
+
51
+ /* Right side bubble - squared bottom right */
52
+ :host([side='right']:not([subsequent])) .bubble {
53
+ border-bottom-right-radius: 0.25rem;
54
+ }
55
+
56
+ @media (max-width: 480px) {
57
+ .bubble {
58
+ font-size: 0.9375rem;
59
+ padding: 0.5rem 0.75rem;
60
+ }
61
+ }
62
+
63
+ /* Dark mode support */
64
+ @media (prefers-color-scheme: dark) {
65
+ :host {
66
+ --bubble-bg: var(--bb-bubble-bg-dark, ${unsafeCSS(THEME.slate[800])});
67
+ --bubble-color: var(--bb-bubble-color-dark, ${unsafeCSS(THEME.slate[100])});
68
+ }
69
+
70
+ :host([side='right']) {
71
+ --bubble-bg: var(--bb-bubble-right-bg-dark, ${unsafeCSS(THEME.slate[700])});
72
+ }
73
+
74
+ .bubble {
75
+ border: 1px solid ${unsafeCSS(THEME.slate[700])};
76
+ }
77
+
78
+ :host([side='right']) .bubble {
79
+ border: none;
80
+ }
81
+ }
82
+ `; }
83
+ _getInlineStyles() {
84
+ const styles = [];
85
+ if (this.customBg) {
86
+ styles.push(`background-color: ${this.customBg}`);
87
+ }
88
+ if (this.customColor) {
89
+ styles.push(`color: ${this.customColor}`);
90
+ }
91
+ return styles.length > 0 ? styles.join('; ') : '';
92
+ }
93
+ render() {
94
+ const style = this._getInlineStyles();
95
+ return html `<div class="bubble" style="${style}">${this.text}</div>`;
96
+ }
97
+ };
98
+ __decorate([
99
+ property()
100
+ ], BBMessageBubble.prototype, "text", void 0);
101
+ __decorate([
102
+ property({ reflect: true })
103
+ ], BBMessageBubble.prototype, "side", void 0);
104
+ __decorate([
105
+ property({ type: Boolean, reflect: true })
106
+ ], BBMessageBubble.prototype, "subsequent", void 0);
107
+ __decorate([
108
+ property({ attribute: 'custom-bg' })
109
+ ], BBMessageBubble.prototype, "customBg", void 0);
110
+ __decorate([
111
+ property({ attribute: 'custom-color' })
112
+ ], BBMessageBubble.prototype, "customColor", void 0);
113
+ BBMessageBubble = __decorate([
114
+ customElement('bb-message-bubble')
115
+ ], BBMessageBubble);
116
+ export { BBMessageBubble };
@@ -0,0 +1,27 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ import './bb-letter-avatar.js';
3
+ import './bb-custom-avatar.js';
4
+ import './bb-message-bubble.js';
5
+ import './bb-timestamp.js';
6
+ /**
7
+ * Message row component - composes avatar, bubble, and timestamp
8
+ */
9
+ export declare class BBMessage extends LitElement {
10
+ static styles: import("lit").CSSResult;
11
+ author: string;
12
+ text: string;
13
+ timestamp: string;
14
+ subsequent: boolean;
15
+ lastInGroup: boolean;
16
+ side: 'left' | 'right';
17
+ private _authors?;
18
+ private _getConfig;
19
+ willUpdate(changedProps: PropertyValues<this>): void;
20
+ private _renderAvatar;
21
+ render(): import("lit").TemplateResult<1>;
22
+ }
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ 'bb-message': BBMessage;
26
+ }
27
+ }
@@ -0,0 +1,174 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, html, css, nothing } from 'lit';
8
+ import { customElement, property, state } from 'lit/decorators.js';
9
+ import { consume } from '@lit/context';
10
+ import { authorContext } from '../contexts/author-context.js';
11
+ import { resolveAuthorConfig } from '../utils/author-resolver.js';
12
+ import { FIRST_CHAR_AVATAR_AUTHORS } from '../const/authors.js';
13
+ import './bb-letter-avatar.js';
14
+ import './bb-custom-avatar.js';
15
+ import './bb-message-bubble.js';
16
+ import './bb-timestamp.js';
17
+ /**
18
+ * Message row component - composes avatar, bubble, and timestamp
19
+ */
20
+ let BBMessage = class BBMessage extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.author = '';
24
+ this.text = '';
25
+ this.timestamp = '';
26
+ this.subsequent = false;
27
+ this.lastInGroup = false;
28
+ }
29
+ static { this.styles = css `
30
+ :host {
31
+ display: flex;
32
+ align-items: flex-end;
33
+ gap: 0.5rem;
34
+ max-width: 80%;
35
+ }
36
+
37
+ :host([side='left']) {
38
+ align-self: flex-start;
39
+ margin-right: auto;
40
+ }
41
+
42
+ :host([side='right']) {
43
+ align-self: flex-end;
44
+ margin-left: auto;
45
+ flex-direction: row-reverse;
46
+ }
47
+
48
+ :host([subsequent]) {
49
+ margin-top: 0.375rem;
50
+ }
51
+
52
+ :host(:not([subsequent])) {
53
+ margin-top: 0.75rem;
54
+ }
55
+
56
+ :host(:first-of-type:not([subsequent])) {
57
+ margin-top: 0;
58
+ }
59
+
60
+ .content {
61
+ display: flex;
62
+ flex-direction: column;
63
+ position: relative;
64
+ padding-bottom: 12px;
65
+ }
66
+
67
+ .avatar-container {
68
+ flex-shrink: 0;
69
+ width: 1.75rem;
70
+ height: 1.75rem;
71
+ }
72
+
73
+ :host([subsequent]) .avatar-container {
74
+ opacity: 0;
75
+ pointer-events: none;
76
+ }
77
+
78
+ @media (max-width: 480px) {
79
+ :host {
80
+ max-width: 85%;
81
+ }
82
+
83
+ .avatar-container {
84
+ width: 1.5rem;
85
+ height: 1.5rem;
86
+ }
87
+ }
88
+ `; }
89
+ _getConfig() {
90
+ return resolveAuthorConfig(this.author, this._authors ?? new Map());
91
+ }
92
+ _renderAvatar(config) {
93
+ const isLetterAvatar = !config.isCustomAvatar || FIRST_CHAR_AVATAR_AUTHORS.has(this.author);
94
+ if (isLetterAvatar) {
95
+ return html `
96
+ <bb-letter-avatar
97
+ class="avatar-container"
98
+ .letter=${this.author.charAt(0).toUpperCase()}
99
+ ></bb-letter-avatar>
100
+ `;
101
+ }
102
+ // Custom avatar - need to parse the avatar HTML
103
+ const avatarContent = config.avatar;
104
+ const isPlainText = !avatarContent.includes('<');
105
+ if (isPlainText) {
106
+ // Single emoji or text - wrap in styled div
107
+ return html `
108
+ <bb-custom-avatar class="avatar-container" .tooltip=${this.author}>
109
+ <div
110
+ style="
111
+ width: 100%;
112
+ height: 100%;
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ font-size: 18px;
117
+ line-height: 1;
118
+ "
119
+ >
120
+ ${avatarContent}
121
+ </div>
122
+ </bb-custom-avatar>
123
+ `;
124
+ }
125
+ // HTML content (SVG, img, etc.)
126
+ return html `
127
+ <bb-custom-avatar class="avatar-container" .tooltip=${this.author}>
128
+ <div .innerHTML=${avatarContent}></div>
129
+ </bb-custom-avatar>
130
+ `;
131
+ }
132
+ render() {
133
+ const config = this._getConfig();
134
+ const side = config.side;
135
+ return html `
136
+ ${this._renderAvatar(config)}
137
+ <div class="content">
138
+ <bb-message-bubble
139
+ .text=${this.text}
140
+ side=${side}
141
+ ?subsequent=${this.subsequent}
142
+ custom-bg=${config.bubbleColor}
143
+ custom-color=${config.textColor}
144
+ ></bb-message-bubble>
145
+ ${this.lastInGroup && this.timestamp
146
+ ? html `<bb-timestamp .value=${this.timestamp} side=${side}></bb-timestamp>`
147
+ : nothing}
148
+ </div>
149
+ `;
150
+ }
151
+ };
152
+ __decorate([
153
+ property()
154
+ ], BBMessage.prototype, "author", void 0);
155
+ __decorate([
156
+ property()
157
+ ], BBMessage.prototype, "text", void 0);
158
+ __decorate([
159
+ property()
160
+ ], BBMessage.prototype, "timestamp", void 0);
161
+ __decorate([
162
+ property({ type: Boolean, reflect: true })
163
+ ], BBMessage.prototype, "subsequent", void 0);
164
+ __decorate([
165
+ property({ type: Boolean, reflect: true })
166
+ ], BBMessage.prototype, "lastInGroup", void 0);
167
+ __decorate([
168
+ consume({ context: authorContext, subscribe: true }),
169
+ state()
170
+ ], BBMessage.prototype, "_authors", void 0);
171
+ BBMessage = __decorate([
172
+ customElement('bb-message')
173
+ ], BBMessage);
174
+ export { BBMessage };
@@ -0,0 +1,111 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ import type { AuthorOptions } from '../types/index.js';
3
+ import type { MessageParser, MessageInput } from '../parsers/base.js';
4
+ import './bb-message.js';
5
+ import './bb-scroll-button.js';
6
+ import './bb-loading-overlay.js';
7
+ /**
8
+ * BBMsgHistory - A chat-style message history web component
9
+ *
10
+ * Uses Lit for reactive rendering with a compositional architecture.
11
+ * Preserves backward compatibility with the lightweight textContent mode.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <!-- Lightweight mode -->
16
+ * <bb-msg-history>
17
+ * alice: Hello!
18
+ * bob: Hi there!
19
+ * </bb-msg-history>
20
+ *
21
+ * <!-- With custom authors -->
22
+ * <bb-msg-history id="chat"></bb-msg-history>
23
+ * <script>
24
+ * document.getElementById('chat')
25
+ * .setAuthor('alice', { avatar: '🐱', side: 'right' });
26
+ * </script>
27
+ * ```
28
+ */
29
+ export declare class BBMsgHistory extends LitElement {
30
+ static styles: import("lit").CSSResult;
31
+ loading: boolean;
32
+ hideScrollBar: boolean;
33
+ infinite: boolean;
34
+ hideScrollButton: boolean;
35
+ theme: 'light' | 'dark' | null;
36
+ private _messages;
37
+ private _processedMessages;
38
+ private _userAuthors;
39
+ private _mutationObserver?;
40
+ private _isParsing;
41
+ private _parser;
42
+ private _scrollController;
43
+ connectedCallback(): void;
44
+ disconnectedCallback(): void;
45
+ willUpdate(changedProps: PropertyValues<BBMsgHistory>): void;
46
+ updated(changedProps: PropertyValues<BBMsgHistory>): void;
47
+ /**
48
+ * Configure an author's avatar, side, and colors.
49
+ * Call before or after rendering — the component re-renders automatically.
50
+ */
51
+ setAuthor(name: string, options: AuthorOptions): this;
52
+ /**
53
+ * Remove a previously set author config.
54
+ */
55
+ removeAuthor(name: string): this;
56
+ /**
57
+ * Show or hide the loading overlay.
58
+ */
59
+ setLoading(isLoading: boolean): this;
60
+ /**
61
+ * Append a message to the history.
62
+ * Automatically scrolls to the new message with smooth animation.
63
+ */
64
+ appendMessage(input: MessageInput): this;
65
+ /**
66
+ * Scroll to the bottom of the message history.
67
+ */
68
+ scrollToBottom(): this;
69
+ /**
70
+ * Set a custom parser for message parsing
71
+ */
72
+ setParser(parser: MessageParser): this;
73
+ /**
74
+ * Initialize MutationObserver for Light DOM observation
75
+ */
76
+ private _initLightDOMObserver;
77
+ /**
78
+ * Parse Light DOM textContent into messages
79
+ */
80
+ private _parseLightDOM;
81
+ /**
82
+ * Check if messages have changed
83
+ */
84
+ private _messagesChanged;
85
+ /**
86
+ * Hide light DOM content visually while keeping it for accessibility/parsing
87
+ */
88
+ private _hideLightDOMContent;
89
+ /**
90
+ * Sync current messages back to light DOM
91
+ */
92
+ private _syncToLightDOM;
93
+ /**
94
+ * Compute message groups for rendering
95
+ */
96
+ private _computeGroups;
97
+ /**
98
+ * Check if two messages can be grouped together
99
+ */
100
+ private _canGroup;
101
+ /**
102
+ * Handle scroll button click
103
+ */
104
+ private _onScrollButtonClick;
105
+ render(): import("lit").TemplateResult<1>;
106
+ }
107
+ declare global {
108
+ interface HTMLElementTagNameMap {
109
+ 'bb-msg-history': BBMsgHistory;
110
+ }
111
+ }