@bbki.ng/bb-msg-history 1.0.0 → 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 (39) hide show
  1. package/dist/components/bb-custom-avatar.d.ts +20 -0
  2. package/dist/components/bb-custom-avatar.js +145 -0
  3. package/dist/components/bb-letter-avatar.d.ts +14 -0
  4. package/dist/components/bb-letter-avatar.js +61 -0
  5. package/dist/components/bb-loading-overlay.d.ts +14 -0
  6. package/dist/components/bb-loading-overlay.js +89 -0
  7. package/dist/components/bb-message-bubble.d.ts +19 -0
  8. package/dist/components/bb-message-bubble.js +116 -0
  9. package/dist/components/bb-message.d.ts +27 -0
  10. package/dist/components/bb-message.js +174 -0
  11. package/dist/components/bb-msg-history.d.ts +111 -0
  12. package/dist/components/bb-msg-history.js +473 -0
  13. package/dist/components/bb-scroll-button.d.ts +16 -0
  14. package/dist/components/bb-scroll-button.js +161 -0
  15. package/dist/components/bb-timestamp.d.ts +15 -0
  16. package/dist/components/bb-timestamp.js +59 -0
  17. package/dist/components/index.d.ts +7 -0
  18. package/dist/components/index.js +7 -0
  19. package/dist/const/styles.js +0 -33
  20. package/dist/contexts/author-context.d.ts +8 -0
  21. package/dist/contexts/author-context.js +6 -0
  22. package/dist/controllers/scroll-controller.d.ts +52 -0
  23. package/dist/controllers/scroll-controller.js +138 -0
  24. package/dist/core/renderer.js +1 -9
  25. package/dist/parsers/base.d.ts +21 -0
  26. package/dist/parsers/base.js +1 -0
  27. package/dist/parsers/default-parser.d.ts +10 -0
  28. package/dist/parsers/default-parser.js +40 -0
  29. package/dist/parsers/index.d.ts +2 -0
  30. package/dist/parsers/index.js +1 -0
  31. package/dist/utils/message-builder.d.ts +0 -4
  32. package/dist/utils/message-builder.js +0 -15
  33. package/dist/utils/tooltip.d.ts +11 -2
  34. package/dist/utils/tooltip.js +56 -13
  35. package/package.json +1 -1
  36. package/src/const/styles.ts +0 -33
  37. package/src/core/renderer.ts +1 -11
  38. package/src/utils/message-builder.ts +0 -15
  39. package/src/utils/tooltip.ts +0 -16
@@ -0,0 +1,161 @@
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
+ * Scroll-to-bottom button component
12
+ * Emits bb-scroll-to-bottom event when clicked
13
+ */
14
+ let BBScrollButton = class BBScrollButton extends LitElement {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.visible = false;
18
+ }
19
+ static { this.styles = css `
20
+ :host {
21
+ display: block;
22
+ position: absolute;
23
+ bottom: 16px;
24
+ left: 50%;
25
+ transform: translateX(-50%);
26
+ z-index: 10;
27
+ }
28
+
29
+ .scroll-btn {
30
+ width: 36px;
31
+ height: 36px;
32
+ border-radius: 50%;
33
+ background: #ffffff;
34
+ border: none;
35
+ color: ${unsafeCSS(THEME.gray[500])};
36
+ cursor: pointer;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ opacity: 0;
41
+ visibility: hidden;
42
+ transform: translateY(10px) scale(0);
43
+ transition:
44
+ opacity 0.2s ease,
45
+ transform 0.2s ease,
46
+ visibility 0.2s ease,
47
+ box-shadow 0.2s ease;
48
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
49
+ }
50
+
51
+ .scroll-btn.visible {
52
+ opacity: 1;
53
+ visibility: visible;
54
+ transform: translateY(0) scale(1);
55
+ }
56
+
57
+ .scroll-btn:hover {
58
+ color: ${unsafeCSS(THEME.gray[700])};
59
+ transform: translateY(-2px) scale(1.05);
60
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
61
+ }
62
+
63
+ .scroll-btn:active {
64
+ transform: translateY(-1px) scale(0.95);
65
+ }
66
+
67
+ .scroll-btn svg {
68
+ width: 20px;
69
+ height: 20px;
70
+ }
71
+
72
+ @media (max-width: 480px) {
73
+ :host {
74
+ bottom: 12px;
75
+ }
76
+
77
+ .scroll-btn {
78
+ width: 32px;
79
+ height: 32px;
80
+ }
81
+
82
+ .scroll-btn svg {
83
+ width: 18px;
84
+ height: 18px;
85
+ }
86
+ }
87
+
88
+ @media (prefers-reduced-motion: reduce) {
89
+ .scroll-btn {
90
+ transition:
91
+ opacity 0.15s ease,
92
+ visibility 0.15s ease;
93
+ transform: translateY(10px) scale(0);
94
+ }
95
+
96
+ .scroll-btn.visible {
97
+ transform: translateY(0) scale(1);
98
+ }
99
+
100
+ .scroll-btn:hover {
101
+ transform: translateY(-2px) scale(1);
102
+ }
103
+
104
+ .scroll-btn:active {
105
+ transform: translateY(0) scale(0.95);
106
+ }
107
+ }
108
+
109
+ /* Dark mode */
110
+ @media (prefers-color-scheme: dark) {
111
+ .scroll-btn {
112
+ background: ${unsafeCSS(THEME.slate[800])};
113
+ color: ${unsafeCSS(THEME.slate[300])};
114
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
115
+ }
116
+
117
+ .scroll-btn:hover {
118
+ color: ${unsafeCSS(THEME.slate[200])};
119
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
120
+ }
121
+ }
122
+ `; }
123
+ _handleClick() {
124
+ this.dispatchEvent(new CustomEvent('bb-scroll-to-bottom', {
125
+ bubbles: true,
126
+ composed: true,
127
+ detail: { behavior: 'smooth' },
128
+ }));
129
+ }
130
+ render() {
131
+ return html `
132
+ <button
133
+ class="scroll-btn ${this.visible ? 'visible' : ''}"
134
+ @click=${this._handleClick}
135
+ aria-label="Scroll to bottom"
136
+ title="Scroll to bottom"
137
+ >
138
+ <svg
139
+ xmlns="http://www.w3.org/2000/svg"
140
+ width="20"
141
+ height="20"
142
+ viewBox="0 0 24 24"
143
+ fill="none"
144
+ stroke="currentColor"
145
+ stroke-width="2"
146
+ stroke-linecap="round"
147
+ stroke-linejoin="round"
148
+ >
149
+ <polyline points="6 9 12 15 18 9"></polyline>
150
+ </svg>
151
+ </button>
152
+ `;
153
+ }
154
+ };
155
+ __decorate([
156
+ property({ type: Boolean, reflect: true })
157
+ ], BBScrollButton.prototype, "visible", void 0);
158
+ BBScrollButton = __decorate([
159
+ customElement('bb-scroll-button')
160
+ ], BBScrollButton);
161
+ export { BBScrollButton };
@@ -0,0 +1,15 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Timestamp component - displays message timestamp
4
+ */
5
+ export declare class BBTimestamp extends LitElement {
6
+ static styles: import("lit").CSSResult;
7
+ value: string;
8
+ side: 'left' | 'right';
9
+ render(): import("lit").TemplateResult<1>;
10
+ }
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'bb-timestamp': BBTimestamp;
14
+ }
15
+ }
@@ -0,0 +1,59 @@
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
+ * Timestamp component - displays message timestamp
12
+ */
13
+ let BBTimestamp = class BBTimestamp extends LitElement {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.value = '';
17
+ this.side = 'left';
18
+ }
19
+ static { this.styles = css `
20
+ :host {
21
+ display: block;
22
+ }
23
+
24
+ .timestamp {
25
+ font-size: 11px;
26
+ color: ${unsafeCSS(THEME.gray[400])};
27
+ white-space: nowrap;
28
+ line-height: 1;
29
+ pointer-events: none;
30
+ }
31
+
32
+ :host([side='left']) .timestamp {
33
+ text-align: left;
34
+ }
35
+
36
+ :host([side='right']) .timestamp {
37
+ text-align: right;
38
+ }
39
+
40
+ @media (prefers-color-scheme: dark) {
41
+ .timestamp {
42
+ color: ${unsafeCSS(THEME.gray[500])};
43
+ }
44
+ }
45
+ `; }
46
+ render() {
47
+ return html `<div class="timestamp">${this.value}</div>`;
48
+ }
49
+ };
50
+ __decorate([
51
+ property()
52
+ ], BBTimestamp.prototype, "value", void 0);
53
+ __decorate([
54
+ property({ reflect: true })
55
+ ], BBTimestamp.prototype, "side", void 0);
56
+ BBTimestamp = __decorate([
57
+ customElement('bb-timestamp')
58
+ ], BBTimestamp);
59
+ export { BBTimestamp };
@@ -0,0 +1,7 @@
1
+ export { BBLetterAvatar } from './bb-letter-avatar.js';
2
+ export { BBCustomAvatar } from './bb-custom-avatar.js';
3
+ export { BBMessageBubble } from './bb-message-bubble.js';
4
+ export { BBTimestamp } from './bb-timestamp.js';
5
+ export { BBMessage } from './bb-message.js';
6
+ export { BBScrollButton } from './bb-scroll-button.js';
7
+ export { BBLoadingOverlay } from './bb-loading-overlay.js';
@@ -0,0 +1,7 @@
1
+ export { BBLetterAvatar } from './bb-letter-avatar.js';
2
+ export { BBCustomAvatar } from './bb-custom-avatar.js';
3
+ export { BBMessageBubble } from './bb-message-bubble.js';
4
+ export { BBTimestamp } from './bb-timestamp.js';
5
+ export { BBMessage } from './bb-message.js';
6
+ export { BBScrollButton } from './bb-scroll-button.js';
7
+ export { BBLoadingOverlay } from './bb-loading-overlay.js';
@@ -159,7 +159,6 @@ export const MAIN_STYLES = `
159
159
  background: #ffffff;
160
160
  border-radius: 50%;
161
161
  overflow: hidden;
162
- cursor: help;
163
162
  }
164
163
 
165
164
  .avatar-wrapper--hidden {
@@ -182,38 +181,6 @@ export const MAIN_STYLES = `
182
181
  height: 100%;
183
182
  }
184
183
 
185
- /* Hover tooltip */
186
- .avatar-tooltip {
187
- position: fixed;
188
- padding: 0.25rem 0.5rem;
189
- background: ${THEME.gray[800]};
190
- color: ${THEME.gray[50]};
191
- font-size: 0.75rem;
192
- border-radius: 0.25rem;
193
- white-space: nowrap;
194
- opacity: 0;
195
- visibility: hidden;
196
- pointer-events: none;
197
- z-index: 10;
198
- font-weight: 500;
199
- letter-spacing: 0.02em;
200
- }
201
-
202
- .avatar-tooltip::after {
203
- content: '';
204
- position: absolute;
205
- top: calc(100% - 1px);
206
- left: 50%;
207
- transform: translateX(-50%);
208
- border: 4px solid transparent;
209
- border-top-color: ${THEME.gray[800]};
210
- }
211
-
212
- .avatar-wrapper:hover .avatar-tooltip {
213
- opacity: 1;
214
- visibility: visible;
215
- }
216
-
217
184
  /* Message content area */
218
185
  .msg-content {
219
186
  display: flex;
@@ -0,0 +1,8 @@
1
+ import type { AuthorOptions } from '../types/index.js';
2
+ /**
3
+ * Lit Context for author configuration sharing
4
+ * Allows sub-components to access author configs without prop drilling
5
+ */
6
+ export declare const authorContext: {
7
+ __context__: Map<string, AuthorOptions>;
8
+ };
@@ -0,0 +1,6 @@
1
+ import { createContext } from '@lit/context';
2
+ /**
3
+ * Lit Context for author configuration sharing
4
+ * Allows sub-components to access author configs without prop drilling
5
+ */
6
+ export const authorContext = createContext('authors');
@@ -0,0 +1,52 @@
1
+ import type { ReactiveController } from 'lit';
2
+ import type { LitElement } from 'lit';
3
+ /**
4
+ * ScrollController - Reactive controller for scroll behavior
5
+ *
6
+ * Encapsulates scroll-related logic:
7
+ * - Scroll to bottom with smooth animation
8
+ * - Detect when user is near/at bottom of content
9
+ * - Control scroll button visibility based on scroll position
10
+ */
11
+ export declare class ScrollController implements ReactiveController {
12
+ private host;
13
+ private _container?;
14
+ private _intersectionObserver?;
15
+ private _resizeObserver?;
16
+ private _isVisible;
17
+ private readonly BOTTOM_THRESHOLD;
18
+ constructor(host: LitElement);
19
+ hostConnected(): void;
20
+ hostDisconnected(): void;
21
+ hostUpdated(): void;
22
+ private _init;
23
+ private _cleanup;
24
+ private _initIntersectionObserver;
25
+ private _initResizeObserver;
26
+ /**
27
+ * Check current scroll position and update visibility state
28
+ */
29
+ checkPosition(): void;
30
+ /**
31
+ * Scroll the container to the bottom
32
+ * @param behavior - Scroll behavior: 'smooth' or 'auto'
33
+ */
34
+ scrollToBottom(behavior?: ScrollBehavior): void;
35
+ /**
36
+ * Check if the container is currently at or near the bottom
37
+ * @returns true if within threshold pixels of bottom
38
+ */
39
+ isAtBottom(): boolean;
40
+ /**
41
+ * Get the current button visibility state
42
+ */
43
+ get isVisible(): boolean;
44
+ /**
45
+ * Get the scrollable container element
46
+ */
47
+ get container(): HTMLElement | undefined;
48
+ /**
49
+ * Update the observed last message (call when messages change)
50
+ */
51
+ updateObservedMessage(): void;
52
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * ScrollController - Reactive controller for scroll behavior
3
+ *
4
+ * Encapsulates scroll-related logic:
5
+ * - Scroll to bottom with smooth animation
6
+ * - Detect when user is near/at bottom of content
7
+ * - Control scroll button visibility based on scroll position
8
+ */
9
+ export class ScrollController {
10
+ constructor(host) {
11
+ this._isVisible = false;
12
+ // Threshold in pixels from bottom to consider "at bottom"
13
+ this.BOTTOM_THRESHOLD = 50;
14
+ this.host = host;
15
+ host.addController(this);
16
+ }
17
+ hostConnected() {
18
+ // Wait for render to complete before querying DOM
19
+ this.host.updateComplete.then(() => {
20
+ this._init();
21
+ });
22
+ }
23
+ hostDisconnected() {
24
+ this._cleanup();
25
+ }
26
+ hostUpdated() {
27
+ // Re-initialize if container becomes available
28
+ if (!this._container) {
29
+ this._init();
30
+ }
31
+ }
32
+ _init() {
33
+ const host = this.host;
34
+ this._container = host.renderRoot?.querySelector('.history-container');
35
+ if (this._container) {
36
+ this._initIntersectionObserver();
37
+ this._initResizeObserver();
38
+ this.checkPosition();
39
+ }
40
+ }
41
+ _cleanup() {
42
+ this._intersectionObserver?.disconnect();
43
+ this._resizeObserver?.disconnect();
44
+ this._container = undefined;
45
+ }
46
+ _initIntersectionObserver() {
47
+ if (!this._container)
48
+ return;
49
+ // Observe the last message to detect if we're at bottom
50
+ const lastMsg = this._container.lastElementChild;
51
+ if (!lastMsg)
52
+ return;
53
+ this._intersectionObserver = new IntersectionObserver(entries => {
54
+ entries.forEach(entry => {
55
+ const wasVisible = this._isVisible;
56
+ this._isVisible = !entry.isIntersecting;
57
+ if (wasVisible !== this._isVisible) {
58
+ this.host.requestUpdate();
59
+ }
60
+ });
61
+ }, {
62
+ root: this._container,
63
+ threshold: 0.1,
64
+ rootMargin: '0px 0px 50px 0px',
65
+ });
66
+ this._intersectionObserver.observe(lastMsg);
67
+ }
68
+ _initResizeObserver() {
69
+ if (!this._container)
70
+ return;
71
+ this._resizeObserver = new ResizeObserver(() => {
72
+ this.checkPosition();
73
+ });
74
+ this._resizeObserver.observe(this._container);
75
+ }
76
+ /**
77
+ * Check current scroll position and update visibility state
78
+ */
79
+ checkPosition() {
80
+ if (!this._container)
81
+ return;
82
+ const isAtBottom = this.isAtBottom();
83
+ const hasOverflow = this._container.scrollHeight > this._container.clientHeight;
84
+ // Show button when not at bottom and content has overflow
85
+ const shouldShow = !isAtBottom && hasOverflow;
86
+ if (shouldShow !== this._isVisible) {
87
+ this._isVisible = shouldShow;
88
+ this.host.requestUpdate();
89
+ }
90
+ }
91
+ /**
92
+ * Scroll the container to the bottom
93
+ * @param behavior - Scroll behavior: 'smooth' or 'auto'
94
+ */
95
+ scrollToBottom(behavior = 'smooth') {
96
+ if (!this._container)
97
+ return;
98
+ this._container.scrollTo({
99
+ top: this._container.scrollHeight,
100
+ behavior,
101
+ });
102
+ // Hide button since we're scrolling to bottom
103
+ if (this._isVisible) {
104
+ this._isVisible = false;
105
+ this.host.requestUpdate();
106
+ }
107
+ }
108
+ /**
109
+ * Check if the container is currently at or near the bottom
110
+ * @returns true if within threshold pixels of bottom
111
+ */
112
+ isAtBottom() {
113
+ if (!this._container)
114
+ return true;
115
+ const distanceFromBottom = this._container.scrollHeight - this._container.scrollTop - this._container.clientHeight;
116
+ return distanceFromBottom < this.BOTTOM_THRESHOLD;
117
+ }
118
+ /**
119
+ * Get the current button visibility state
120
+ */
121
+ get isVisible() {
122
+ return this._isVisible;
123
+ }
124
+ /**
125
+ * Get the scrollable container element
126
+ */
127
+ get container() {
128
+ return this._container;
129
+ }
130
+ /**
131
+ * Update the observed last message (call when messages change)
132
+ */
133
+ updateObservedMessage() {
134
+ this._intersectionObserver?.disconnect();
135
+ this._initIntersectionObserver();
136
+ this.checkPosition();
137
+ }
138
+ }
@@ -1,8 +1,7 @@
1
1
  import { EMPTY_STYLES, LOADING_STYLES, MAIN_STYLES } from '../const/styles.js';
2
2
  import { resolveAuthorConfig } from '../utils/author-resolver.js';
3
- import { buildMessageRowHtml, setupTooltipForElement } from '../utils/message-builder.js';
3
+ import { buildMessageRowHtml } from '../utils/message-builder.js';
4
4
  import { buildScrollButtonHtml } from '../utils/scroll-button.js';
5
- import { setupTooltips } from '../utils/tooltip.js';
6
5
  /**
7
6
  * Renderer - Manages all DOM rendering operations
8
7
  *
@@ -88,8 +87,6 @@ export class Renderer {
88
87
  if (wasAtBottom) {
89
88
  scrollContainer.scrollTop = scrollContainer.scrollHeight;
90
89
  }
91
- // Re-setup tooltips for new content
92
- setupTooltips(this.shadowRoot);
93
90
  return { wasAtBottom };
94
91
  }
95
92
  /**
@@ -126,11 +123,6 @@ export class Renderer {
126
123
  lastGroupTimestamp, true // isLastInGroup - when appending, this is always last (for now)
127
124
  );
128
125
  container.insertAdjacentHTML('beforeend', msgHtml);
129
- // Setup tooltip for new element
130
- const newWrapper = container.lastElementChild?.querySelector('.avatar-wrapper');
131
- if (newWrapper) {
132
- setupTooltipForElement(newWrapper);
133
- }
134
126
  return {
135
127
  success: true,
136
128
  lastAuthor: message.author,
@@ -0,0 +1,21 @@
1
+ import type { Message } from '../types/index.js';
2
+ /**
3
+ * Parser interface for message parsing
4
+ * Allows custom parsers to be plugged in
5
+ */
6
+ export interface MessageParser {
7
+ /**
8
+ * Parse text content into message array
9
+ * @param textContent - Raw text content to parse
10
+ * @returns Array of parsed messages
11
+ */
12
+ parse(textContent: string | null): Message[];
13
+ }
14
+ /**
15
+ * Input type for appending messages
16
+ */
17
+ export interface MessageInput {
18
+ author: string;
19
+ text: string;
20
+ timestamp?: string;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { Message } from '../types/index.js';
2
+ import type { MessageParser } from './base.js';
3
+ /**
4
+ * Default message parser implementation
5
+ * Format: `[timestamp] author: text` or `author: text` (one message per line)
6
+ * Timestamp is optional for backward compatibility
7
+ */
8
+ export declare class DefaultMessageParser implements MessageParser {
9
+ parse(textContent: string | null): Message[];
10
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Default message parser implementation
3
+ * Format: `[timestamp] author: text` or `author: text` (one message per line)
4
+ * Timestamp is optional for backward compatibility
5
+ */
6
+ export class DefaultMessageParser {
7
+ parse(textContent) {
8
+ const raw = textContent || '';
9
+ const messages = [];
10
+ // Pattern: [timestamp] author: text (space between timestamp and author is optional)
11
+ const timestampPattern = /^\[([^\]]+)\]\s*(.+)$/;
12
+ for (const line of raw.split('\n')) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed)
15
+ continue;
16
+ let remainingLine = trimmed;
17
+ let timestamp;
18
+ // Try to extract timestamp
19
+ const timestampMatch = trimmed.match(timestampPattern);
20
+ if (timestampMatch) {
21
+ timestamp = timestampMatch[1].trim();
22
+ remainingLine = timestampMatch[2];
23
+ }
24
+ // Find the author:text separator (colon followed by space)
25
+ const colonIdx = remainingLine.indexOf(':');
26
+ if (colonIdx <= 0)
27
+ continue;
28
+ const author = remainingLine.slice(0, colonIdx).trim();
29
+ const text = remainingLine.slice(colonIdx + 1).trim();
30
+ if (author && text) {
31
+ const message = { author, text };
32
+ if (timestamp) {
33
+ message.timestamp = timestamp;
34
+ }
35
+ messages.push(message);
36
+ }
37
+ }
38
+ return messages;
39
+ }
40
+ }
@@ -0,0 +1,2 @@
1
+ export type { MessageParser, MessageInput } from './base.js';
2
+ export { DefaultMessageParser } from './default-parser.js';
@@ -0,0 +1 @@
1
+ export { DefaultMessageParser } from './default-parser.js';
@@ -11,7 +11,3 @@ export declare function buildTimestampHtml(timestamp: string, side: 'left' | 'ri
11
11
  * Build a single message row HTML string
12
12
  */
13
13
  export declare function buildMessageRowHtml(author: string, text: string, config: AuthorConfig, isSubsequent: boolean, timestamp?: string, isLastInGroup?: boolean): string;
14
- /**
15
- * Setup tooltip for a single avatar wrapper element
16
- */
17
- export declare function setupTooltipForElement(wrapper: Element): void;
@@ -8,7 +8,6 @@ export function buildAvatarHtml(author, config, showAvatar) {
8
8
  <div class="avatar-wrapper ${showAvatar ? '' : 'avatar-wrapper--hidden'}"
9
9
  data-author="${escapeHtml(author)}">
10
10
  <div class="avatar">${config.avatar}</div>
11
- <div class="avatar-tooltip">${escapeHtml(author)}</div>
12
11
  </div>
13
12
  `;
14
13
  }
@@ -53,17 +52,3 @@ export function buildMessageRowHtml(author, text, config, isSubsequent, timestam
53
52
  </div>
54
53
  `;
55
54
  }
56
- /**
57
- * Setup tooltip for a single avatar wrapper element
58
- */
59
- export function setupTooltipForElement(wrapper) {
60
- wrapper.addEventListener('mouseenter', () => {
61
- const tooltip = wrapper.querySelector('.avatar-tooltip');
62
- if (!tooltip)
63
- return;
64
- const rect = wrapper.getBoundingClientRect();
65
- const tooltipRect = tooltip.getBoundingClientRect();
66
- tooltip.style.left = `${rect.left + rect.width / 2 - tooltipRect.width / 2}px`;
67
- tooltip.style.top = `${rect.top - tooltipRect.height - 8}px`;
68
- });
69
- }
@@ -1,5 +1,14 @@
1
1
  /**
2
- * Setup dynamic tooltip positioning
3
- * Tooltips are positioned fixed to avoid overflow clipping from parent containers
2
+ * Tooltip utilities for avatar hover effects
3
+ *
4
+ * Tooltips use position: fixed to escape overflow clipping from scrollable containers.
5
+ * Position is calculated on mouseenter and updated on scroll to prevent staleness.
6
+ */
7
+ /**
8
+ * Setup tooltip positioning for a single avatar wrapper element
9
+ */
10
+ export declare function setupTooltipForElement(wrapper: Element): void;
11
+ /**
12
+ * Setup tooltips for all avatar wrappers in the shadow root
4
13
  */
5
14
  export declare function setupTooltips(shadowRoot: ShadowRoot): void;