@bloonio/chat-angular 0.1.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Backwards compatible with 0.1.x.
6
+
7
+ ### Added
8
+
9
+ - `locale` (`'fr' | 'en' | 'zh'`, default `'en'`): built-in translations for
10
+ every string the widget renders itself, also sent with each new visitor
11
+ session. It may be a function or a signal, and `provideBloonioChat` accepts
12
+ a factory run in an injection context, so the widget can follow the host
13
+ app's language at runtime. With no `locale`, sessions are still created
14
+ with `fr`, as in 0.1.x.
15
+ - Host copy: `greeting`, `launcherLabel`, `onlineStatusText`,
16
+ `offlineStatusText`, `quickReplies`. Each replaces the matching text of the
17
+ tenant's single-language relay config and may be a function or a signal.
18
+ The greeting is a local opening bubble, never sent or stored.
19
+ - Unread badge on the launcher for answers received while the panel is
20
+ closed; the count is in its accessible name.
21
+ - Resume after a reload: the visitor session is stored per tenant in
22
+ `localStorage`; on load the token is refreshed, the thread replayed and the
23
+ socket reopened. A session the relay refuses is replaced by a fresh one
24
+ without an error. Not applied when a `visitorTokenProvider` is configured.
25
+ - The panel says so when the chat is connecting or cannot be reached, and
26
+ reopening it retries. The input is disabled while there is no session.
27
+ - Accessibility: `aria-expanded` on the launcher, the floating panel is a
28
+ labelled dialog that takes focus on open, Escape closes it and focus
29
+ returns to the launcher, the message list is a `log` region, the input bar
30
+ shows keyboard focus.
31
+ - `BloonioChatService.visibleMessages`.
32
+ - Specs (`npm test`).
33
+
34
+ ### Fixed
35
+
36
+ - Bubbles are aligned again (visitor right, answers left, notices centred):
37
+ the message list host no longer sits between the flex column and its
38
+ items.
39
+ - The typing dots clear when the answer arrives, and after 30 seconds
40
+ without a new typing frame.
41
+ - The input and the quick replies wait for the socket's welcome frame, so a
42
+ message can no longer be typed or tapped before it can be sent
43
+ (`connecting` now lasts until then).
44
+ - Messages received while the panel was open no longer count as unread once
45
+ it is closed.
46
+ - A socket the relay closes with 4401, 4403 or 4404 is no longer retried
47
+ forever with the same token.
48
+ - The header initials are styled from the stylesheet, not a style attribute
49
+ (a strict CSP dropped it).
50
+
51
+ ### Changed
52
+
53
+ - The ticket notice no longer carries an emoji or a dash, and the agent
54
+ joined / left notices lose their final period.
package/README.md CHANGED
@@ -46,22 +46,135 @@ export const appConfig: ApplicationConfig = {
46
46
  ```
47
47
 
48
48
  ```ts
49
- // any standalone component — drop the launcher anywhere
50
- import { BloonioChatLauncherComponent } from '@bloonio/chat-angular';
49
+ // any standalone component — the launcher toggles the panel next to it
50
+ import {
51
+ BloonioChatLauncherComponent,
52
+ BloonioChatPanelComponent,
53
+ } from '@bloonio/chat-angular';
51
54
 
52
55
  @Component({
53
56
  standalone: true,
54
- imports: [BloonioChatLauncherComponent],
55
- template: `<router-outlet /> <bloonio-chat-launcher />`,
57
+ imports: [BloonioChatLauncherComponent, BloonioChatPanelComponent],
58
+ template: `<router-outlet /> <bloonio-chat-launcher /> <bloonio-chat-panel />`,
56
59
  })
57
60
  export class AppComponent {}
58
61
  ```
59
62
 
63
+ ## Language
64
+
65
+ `locale` picks the built-in translation of every string the widget renders
66
+ itself (placeholder, labels, connection and error lines, agent and ticket
67
+ notices, aria-labels). Supported: `'fr'`, `'en'`, `'zh'`. Default `'en'`; a
68
+ region is trimmed (`'fr-FR'` is `'fr'`) and anything else falls back to
69
+ English.
70
+
71
+ The configured value is also sent, as given, with each new visitor session.
72
+ With no `locale` configured the session is created with `fr`, as in 0.1.x.
73
+
74
+ Text configured on the relay (status texts, launcher label, offline message,
75
+ quick replies) is shown as received, unless the host supplies its own: see
76
+ "Host copy" below.
77
+
78
+ To follow the host app's language at runtime, pass a function. A signal is
79
+ one, and `provideBloonioChat` accepts a factory that runs in an injection
80
+ context so it can reach your own language service:
81
+
82
+ ```ts
83
+ provideBloonioChat(() => ({
84
+ tenantId: '019e4ae7-…',
85
+ publicKey: 'pk_…',
86
+ baseUrl: 'https://chat-relay.example.com',
87
+ locale: inject(MyI18nService).lang, // Signal<'fr' | 'en'>
88
+ })),
89
+ ```
90
+
91
+ The widget's strings switch immediately. A conversation keeps the language
92
+ it was opened with.
93
+
94
+ ## Host copy
95
+
96
+ A tenant's relay config holds one language. These optional entries replace
97
+ the matching tenant text; each takes a value or a function (a signal works),
98
+ so it can follow the host's language. Left out, the tenant's text is used.
99
+
100
+ | Entry | Replaces |
101
+ |---|---|
102
+ | `greeting` | Nothing on the relay: an opening bubble drawn like an assistant message at the top of a new conversation. It is local only (never sent, never stored), stays above the first messages, and is not shown above a thread replayed after a reload. When set, the tenant's offline message is not shown. |
103
+ | `launcherLabel` | The label next to the launcher. An empty string shows no label. |
104
+ | `onlineStatusText` | The header subtitle during opening hours. |
105
+ | `offlineStatusText` | The header subtitle outside opening hours. |
106
+ | `quickReplies` | The suggested replies shown until the visitor's first message. |
107
+
108
+ ```ts
109
+ provideBloonioChat(() => {
110
+ const i18n = inject(MyI18nService);
111
+ return {
112
+ tenantId: '019e4ae7-…',
113
+ publicKey: 'pk_…',
114
+ baseUrl: 'https://chat-relay.example.com',
115
+ locale: i18n.lang,
116
+ launcherLabel: '',
117
+ greeting: () => i18n.t('chat.greeting'),
118
+ onlineStatusText: () => i18n.t('chat.online'),
119
+ quickReplies: () => [i18n.t('chat.reply.sales'), i18n.t('chat.reply.help')],
120
+ };
121
+ }),
122
+ ```
123
+
124
+ ## Resume after a reload
125
+
126
+ The visitor session (`session_id`, `visitor_token`, `expires_at`) is kept in
127
+ `localStorage` under `bloonio:chat:<tenantId>:session`. On the next page
128
+ load the widget refreshes the token (`POST /refresh/visitor-token`), replays
129
+ the newest 50 messages (`GET /fetch/visitor-messages`) and reopens the
130
+ socket in the background, so a visitor waiting for an agent keeps the
131
+ thread and still receives the reply with the panel closed.
132
+
133
+ When the session cannot be resumed (the relay answers 4xx, or closes the
134
+ socket with 4401 / 4404) the stored session is dropped and a new one starts
135
+ the next time the panel is open, with no error shown. A network failure
136
+ keeps the stored session for the next load. Nothing is read or written on
137
+ the server, and when storage is blocked the chat works without resume.
138
+
139
+ Sessions are not stored when a `visitorTokenProvider` is configured: an
140
+ identified visitor's thread would otherwise show to the next account on the
141
+ same browser. An anonymous thread stays resumable on that browser for as
142
+ long as the relay keeps the session, shared computers included.
143
+
144
+ ## Theming
145
+
146
+ Colours, radius, shadow and font are CSS variables declared on `.bch-root`.
147
+ Redeclare them in a rule placed after the stylesheet import:
148
+
149
+ ```css
150
+ @import "@bloonio/chat-angular/styles.css";
151
+
152
+ .bch-root {
153
+ --bch-primary: #0b5d3b;
154
+ --bch-bg: #ffffff;
155
+ --bch-font: "Inter", sans-serif;
156
+ }
157
+ ```
158
+
159
+ Variables: `--bch-primary`, `--bch-primary-contrast`, `--bch-bg`,
160
+ `--bch-surface`, `--bch-text`, `--bch-text-muted`, `--bch-border`,
161
+ `--bch-radius`, `--bch-shadow`, `--bch-font`. The tenant's branding colour,
162
+ when set on the relay, is applied inline as `--bch-primary` on `.bch-root`
163
+ and `.bch-panel`; mark your own value `!important` on both to keep it.
164
+
165
+ ## Accessibility
166
+
167
+ The launcher is a labelled toggle (`aria-expanded`). Answers received while
168
+ the panel is closed show as a count badge on it, and the count is part of
169
+ its accessible name. The floating panel is a non-modal dialog: opening moves
170
+ focus into it, Escape or its close button closes it and returns focus to the
171
+ launcher, and new messages are announced through a `log` region.
172
+
60
173
  ## Three rendering modes
61
174
 
62
175
  | Mode | Component | When |
63
176
  |---|---|---|
64
- | **Floating** | `<bloonio-chat-launcher />` (+ the launcher renders the panel itself on toggle) | Tidio-style — a bubble in the corner that expands on click. |
177
+ | **Floating** | `<bloonio-chat-launcher />` + `<bloonio-chat-panel />` | Tidio-style — a bubble in the corner that expands on click. |
65
178
  | **Docked panel** | `<bloonio-chat-panel mode="docked" />` | Always-on chat in a sidebar / dedicated column. No floating chrome. |
66
179
  | **Headless thread** | `<bloonio-chat-thread />` | Just messages + input, no header. Embed inside your own support pages. |
67
180
 
@@ -98,7 +211,8 @@ For tenants on `@angular/ssr`, no extra configuration is needed.
98
211
  ```bash
99
212
  npm install
100
213
  npm run build # ng-packagr → dist/ (fesm2022 + .d.ts + Ivy partial)
101
- npm run typecheck # tsc --noEmit -p tsconfig.lib.json
214
+ npm run typecheck # tsc --noEmit (sources + specs)
215
+ npm test # vitest (jsdom): locale, resume, focus handling
102
216
  ```
103
217
 
104
218
  Every component uses inline `template:` strings, so there are no separate