@callembed/elements 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +88 -2
- package/dist/callembed-elements.iife.js +125 -0
- package/dist/index.cjs +555 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +527 -0
- package/package.json +81 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sdk = require('@callembed/sdk');
|
|
4
|
+
var ui = require('@callembed/sdk/ui');
|
|
5
|
+
|
|
6
|
+
// src/callembed-button.ts
|
|
7
|
+
function statusLabel(copy, uiState, sdkStatus) {
|
|
8
|
+
switch (uiState) {
|
|
9
|
+
case "idle":
|
|
10
|
+
return copy.ready;
|
|
11
|
+
case "ringing":
|
|
12
|
+
return sdkStatus === "connecting" ? copy.connecting : copy.ringing;
|
|
13
|
+
case "connected":
|
|
14
|
+
return copy.answered;
|
|
15
|
+
case "ended":
|
|
16
|
+
return copy.ended;
|
|
17
|
+
case "error":
|
|
18
|
+
return copy.error;
|
|
19
|
+
default:
|
|
20
|
+
return copy.ready;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function runtimeForCopy(locale, copyOverrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
publishableKey: "",
|
|
26
|
+
apiBaseUrl: "",
|
|
27
|
+
locale,
|
|
28
|
+
copyOverrides,
|
|
29
|
+
branding: true,
|
|
30
|
+
debug: false
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/styles.ts
|
|
35
|
+
var ELEMENT_STYLES = `
|
|
36
|
+
:where(.ce-el-button, .ce-el-status, .ce-el-mute) {
|
|
37
|
+
font-family: var(
|
|
38
|
+
--ce-font,
|
|
39
|
+
"Noto Sans TC",
|
|
40
|
+
"IBM Plex Sans",
|
|
41
|
+
"Segoe UI",
|
|
42
|
+
sans-serif
|
|
43
|
+
);
|
|
44
|
+
color: var(--ce-fg, #e8eef4);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ce-el-root {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: flex-start;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ce-el-button {
|
|
55
|
+
appearance: none;
|
|
56
|
+
display: inline-block;
|
|
57
|
+
border: 1px solid var(--ce-border, #2a3a46);
|
|
58
|
+
border-radius: var(--ce-radius, 0);
|
|
59
|
+
background: color-mix(in srgb, var(--ce-accent, #5eb0d8) 25%, transparent);
|
|
60
|
+
color: var(--ce-fg, #e8eef4);
|
|
61
|
+
padding: 0.7rem 1.1rem;
|
|
62
|
+
font-size: inherit;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ce-el-button:hover {
|
|
67
|
+
border-color: var(--ce-accent, #5eb0d8);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ce-el-button:disabled {
|
|
71
|
+
opacity: 0.5;
|
|
72
|
+
cursor: not-allowed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.ce-el-button[data-state="ringing"],
|
|
76
|
+
.ce-el-button[data-state="connected"] {
|
|
77
|
+
border-color: color-mix(
|
|
78
|
+
in srgb,
|
|
79
|
+
var(--ce-danger, #c44b4b) 55%,
|
|
80
|
+
var(--ce-border, #2a3a46)
|
|
81
|
+
);
|
|
82
|
+
color: var(--ce-error, #f0c4c4);
|
|
83
|
+
background: transparent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ce-el-status {
|
|
87
|
+
margin: 0;
|
|
88
|
+
color: var(--ce-muted, #9aadb8);
|
|
89
|
+
font-size: 0.9rem;
|
|
90
|
+
line-height: 1.4;
|
|
91
|
+
min-height: 1.25em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ce-el-status[data-tone="error"] {
|
|
95
|
+
color: var(--ce-error, #f0c4c4);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ce-el-status[data-tone="ok"] {
|
|
99
|
+
color: var(--ce-ok, #9fd6c0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ce-el-mute {
|
|
103
|
+
appearance: none;
|
|
104
|
+
border: 1px solid var(--ce-border, #2a3a46);
|
|
105
|
+
border-radius: var(--ce-radius, 0);
|
|
106
|
+
background: transparent;
|
|
107
|
+
color: inherit;
|
|
108
|
+
padding: 0.45rem 0.75rem;
|
|
109
|
+
font: inherit;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ce-el-mute:hover {
|
|
114
|
+
border-color: var(--ce-accent, #5eb0d8);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.ce-el-mute:disabled {
|
|
118
|
+
opacity: 0.5;
|
|
119
|
+
cursor: not-allowed;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
// src/types.ts
|
|
124
|
+
function toUiState(status) {
|
|
125
|
+
switch (status) {
|
|
126
|
+
case "connecting":
|
|
127
|
+
case "ringing":
|
|
128
|
+
return "ringing";
|
|
129
|
+
case "answered":
|
|
130
|
+
return "connected";
|
|
131
|
+
case "ended":
|
|
132
|
+
return "ended";
|
|
133
|
+
case "error":
|
|
134
|
+
return "error";
|
|
135
|
+
case "idle":
|
|
136
|
+
default:
|
|
137
|
+
return "idle";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/callembed-button.ts
|
|
142
|
+
var TAG = "callembed-button";
|
|
143
|
+
function toErrorState(err) {
|
|
144
|
+
if (err instanceof sdk.CallEmbedError) {
|
|
145
|
+
return { code: err.code, message: err.message };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
code: "call_failed",
|
|
149
|
+
message: err instanceof Error ? err.message : String(err)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function isActiveStatus(status) {
|
|
153
|
+
return status === "connecting" || status === "ringing" || status === "answered";
|
|
154
|
+
}
|
|
155
|
+
function parseLocale(raw) {
|
|
156
|
+
if (raw === "en" || raw === "zh-TW") return raw;
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
var CallEmbedButtonElement = class extends HTMLElement {
|
|
160
|
+
static get observedAttributes() {
|
|
161
|
+
return [
|
|
162
|
+
"publishable-key",
|
|
163
|
+
"api-base-url",
|
|
164
|
+
"locale",
|
|
165
|
+
"queue",
|
|
166
|
+
"label",
|
|
167
|
+
"show-status",
|
|
168
|
+
"show-mute",
|
|
169
|
+
"branding",
|
|
170
|
+
"debug"
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
#status = "idle";
|
|
174
|
+
#callId = null;
|
|
175
|
+
#muted = false;
|
|
176
|
+
#error = null;
|
|
177
|
+
#locale = "en";
|
|
178
|
+
#busy = false;
|
|
179
|
+
#call = null;
|
|
180
|
+
#unbind = null;
|
|
181
|
+
#root = null;
|
|
182
|
+
#styleEl = null;
|
|
183
|
+
#initialized = false;
|
|
184
|
+
#attrInitKey = null;
|
|
185
|
+
get uiState() {
|
|
186
|
+
return toUiState(this.#status);
|
|
187
|
+
}
|
|
188
|
+
get callStatus() {
|
|
189
|
+
return this.#status;
|
|
190
|
+
}
|
|
191
|
+
get callId() {
|
|
192
|
+
return this.#callId;
|
|
193
|
+
}
|
|
194
|
+
get muted() {
|
|
195
|
+
return this.#muted;
|
|
196
|
+
}
|
|
197
|
+
get error() {
|
|
198
|
+
return this.#error;
|
|
199
|
+
}
|
|
200
|
+
connectedCallback() {
|
|
201
|
+
this.#ensureDom();
|
|
202
|
+
this.#syncFromAttributes(
|
|
203
|
+
/* hangUpOnEndpointChange */
|
|
204
|
+
false
|
|
205
|
+
);
|
|
206
|
+
this.#render();
|
|
207
|
+
}
|
|
208
|
+
disconnectedCallback() {
|
|
209
|
+
this.#hangUpActive();
|
|
210
|
+
}
|
|
211
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
212
|
+
if (oldValue === newValue) return;
|
|
213
|
+
if (!this.isConnected) return;
|
|
214
|
+
const endpointChanged = name === "publishable-key" || name === "api-base-url";
|
|
215
|
+
this.#syncFromAttributes(endpointChanged);
|
|
216
|
+
this.#render();
|
|
217
|
+
}
|
|
218
|
+
/** Start a queue call (customer-service ring → agent answers). */
|
|
219
|
+
async start() {
|
|
220
|
+
return this.#start();
|
|
221
|
+
}
|
|
222
|
+
/** Hang up the active call (idempotent). */
|
|
223
|
+
async end() {
|
|
224
|
+
return this.#end();
|
|
225
|
+
}
|
|
226
|
+
async setMuted(muted) {
|
|
227
|
+
const call = this.#call ?? sdk.CallEmbed.getActiveCall();
|
|
228
|
+
if (!call) {
|
|
229
|
+
this.#muted = muted;
|
|
230
|
+
this.#emitState();
|
|
231
|
+
this.#render();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
await call.setMuted(muted);
|
|
235
|
+
}
|
|
236
|
+
#ensureDom() {
|
|
237
|
+
if (this.#root) return;
|
|
238
|
+
this.#styleEl = document.createElement("style");
|
|
239
|
+
this.#styleEl.textContent = ELEMENT_STYLES;
|
|
240
|
+
this.appendChild(this.#styleEl);
|
|
241
|
+
this.#root = document.createElement("div");
|
|
242
|
+
this.#root.className = "ce-el-root";
|
|
243
|
+
this.#root.setAttribute("data-ce-el", "");
|
|
244
|
+
this.appendChild(this.#root);
|
|
245
|
+
}
|
|
246
|
+
#syncFromAttributes(hangUpOnEndpointChange) {
|
|
247
|
+
const publishableKey = this.getAttribute("publishable-key") ?? "";
|
|
248
|
+
const apiBaseUrl = this.getAttribute("api-base-url");
|
|
249
|
+
const locale = parseLocale(this.getAttribute("locale"));
|
|
250
|
+
const brandingAttr = this.getAttribute("branding");
|
|
251
|
+
const branding = brandingAttr !== "false";
|
|
252
|
+
const debug = this.getAttribute("debug") === "true";
|
|
253
|
+
const initKey = JSON.stringify({
|
|
254
|
+
publishableKey,
|
|
255
|
+
apiBaseUrl: apiBaseUrl ?? "",
|
|
256
|
+
locale: locale ?? null,
|
|
257
|
+
branding,
|
|
258
|
+
debug
|
|
259
|
+
});
|
|
260
|
+
if (hangUpOnEndpointChange && this.#attrInitKey !== null) {
|
|
261
|
+
const prev = JSON.parse(this.#attrInitKey);
|
|
262
|
+
if (prev.publishableKey !== publishableKey || prev.apiBaseUrl !== (apiBaseUrl ?? "")) {
|
|
263
|
+
this.#hangUpActive();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (initKey === this.#attrInitKey && this.#initialized) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.#attrInitKey = initKey;
|
|
270
|
+
if (!publishableKey || apiBaseUrl === null) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
sdk.CallEmbed.init({
|
|
275
|
+
publishableKey,
|
|
276
|
+
apiBaseUrl,
|
|
277
|
+
locale,
|
|
278
|
+
branding,
|
|
279
|
+
debug
|
|
280
|
+
});
|
|
281
|
+
const runtime = sdk.CallEmbed._getRuntime();
|
|
282
|
+
if (runtime) this.#locale = runtime.locale;
|
|
283
|
+
this.#initialized = true;
|
|
284
|
+
} catch (err) {
|
|
285
|
+
const state = toErrorState(err);
|
|
286
|
+
this.#error = state;
|
|
287
|
+
this.#status = "error";
|
|
288
|
+
this.#emitError(state);
|
|
289
|
+
this.#emitState();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
#hangUpActive() {
|
|
293
|
+
this.#unbind?.();
|
|
294
|
+
this.#unbind = null;
|
|
295
|
+
const call = this.#call ?? sdk.CallEmbed.getActiveCall();
|
|
296
|
+
this.#call = null;
|
|
297
|
+
if (call && isActiveStatus(call.status)) {
|
|
298
|
+
void call.hangUp();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async #start() {
|
|
302
|
+
try {
|
|
303
|
+
const queue = this.getAttribute("queue") ?? "";
|
|
304
|
+
if (!queue.trim()) {
|
|
305
|
+
throw new sdk.CallEmbedError(
|
|
306
|
+
"invalid_queue",
|
|
307
|
+
"queue is required (set the queue attribute)."
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
this.#error = null;
|
|
311
|
+
this.#muted = false;
|
|
312
|
+
const call = await sdk.CallEmbed.startCall({
|
|
313
|
+
queue,
|
|
314
|
+
hidePanel: true,
|
|
315
|
+
on: {
|
|
316
|
+
statusChange: ({ status }) => {
|
|
317
|
+
this.#status = status;
|
|
318
|
+
this.#emitState();
|
|
319
|
+
this.#render();
|
|
320
|
+
},
|
|
321
|
+
muteChange: ({ muted }) => {
|
|
322
|
+
this.#muted = muted;
|
|
323
|
+
this.#emitState();
|
|
324
|
+
this.#render();
|
|
325
|
+
},
|
|
326
|
+
error: ({ code, message, callId }) => {
|
|
327
|
+
this.#error = { code, message };
|
|
328
|
+
this.#status = "error";
|
|
329
|
+
if (callId && callId !== "pending") this.#callId = callId;
|
|
330
|
+
this.#emitError(this.#error);
|
|
331
|
+
this.#emitState();
|
|
332
|
+
this.#render();
|
|
333
|
+
},
|
|
334
|
+
ended: ({ callId }) => {
|
|
335
|
+
this.#status = "ended";
|
|
336
|
+
if (callId && callId !== "pending") this.#callId = callId;
|
|
337
|
+
this.#emitState();
|
|
338
|
+
this.#render();
|
|
339
|
+
},
|
|
340
|
+
ringing: ({ callId }) => {
|
|
341
|
+
this.#callId = callId;
|
|
342
|
+
this.#emitState();
|
|
343
|
+
},
|
|
344
|
+
answered: ({ callId }) => {
|
|
345
|
+
this.#callId = callId;
|
|
346
|
+
this.#emitState();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
this.#unbind?.();
|
|
351
|
+
this.#call = call;
|
|
352
|
+
this.#status = call.status;
|
|
353
|
+
if (call.callId !== "pending") this.#callId = call.callId;
|
|
354
|
+
this.#unbind = this.#bindCall(call);
|
|
355
|
+
this.#emitState();
|
|
356
|
+
this.#render();
|
|
357
|
+
return call;
|
|
358
|
+
} catch (err) {
|
|
359
|
+
const state = toErrorState(err);
|
|
360
|
+
this.#error = state;
|
|
361
|
+
this.#status = "error";
|
|
362
|
+
this.#emitError(state);
|
|
363
|
+
this.#emitState();
|
|
364
|
+
this.#render();
|
|
365
|
+
if (err instanceof sdk.CallEmbedError) throw err;
|
|
366
|
+
throw new sdk.CallEmbedError("call_failed", state.message);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async #end() {
|
|
370
|
+
const call = this.#call ?? sdk.CallEmbed.getActiveCall();
|
|
371
|
+
if (!call) {
|
|
372
|
+
this.#status = "idle";
|
|
373
|
+
this.#emitState();
|
|
374
|
+
this.#render();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
await call.hangUp();
|
|
378
|
+
}
|
|
379
|
+
#bindCall(call) {
|
|
380
|
+
const offs = [
|
|
381
|
+
call.on("statusChange", ({ status }) => {
|
|
382
|
+
this.#status = status;
|
|
383
|
+
if (call.callId && call.callId !== "pending") {
|
|
384
|
+
this.#callId = call.callId;
|
|
385
|
+
}
|
|
386
|
+
this.#emitState();
|
|
387
|
+
this.#render();
|
|
388
|
+
}),
|
|
389
|
+
call.on("muteChange", ({ muted }) => {
|
|
390
|
+
this.#muted = muted;
|
|
391
|
+
this.#emitState();
|
|
392
|
+
this.#render();
|
|
393
|
+
}),
|
|
394
|
+
call.on("error", ({ code, message, callId }) => {
|
|
395
|
+
this.#error = { code, message };
|
|
396
|
+
this.#status = "error";
|
|
397
|
+
if (callId && callId !== "pending") this.#callId = callId;
|
|
398
|
+
this.#emitError(this.#error);
|
|
399
|
+
this.#emitState();
|
|
400
|
+
this.#render();
|
|
401
|
+
}),
|
|
402
|
+
call.on("ended", ({ callId }) => {
|
|
403
|
+
this.#status = "ended";
|
|
404
|
+
if (callId && callId !== "pending") this.#callId = callId;
|
|
405
|
+
this.#emitState();
|
|
406
|
+
this.#render();
|
|
407
|
+
})
|
|
408
|
+
];
|
|
409
|
+
return () => {
|
|
410
|
+
offs.forEach((off) => off());
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
#emitState() {
|
|
414
|
+
this.dispatchEvent(
|
|
415
|
+
new CustomEvent("callembed:state", {
|
|
416
|
+
bubbles: true,
|
|
417
|
+
composed: true,
|
|
418
|
+
detail: {
|
|
419
|
+
uiState: this.uiState,
|
|
420
|
+
status: this.#status,
|
|
421
|
+
callId: this.#callId,
|
|
422
|
+
muted: this.#muted,
|
|
423
|
+
error: this.#error
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
#emitError(error) {
|
|
429
|
+
this.dispatchEvent(
|
|
430
|
+
new CustomEvent("callembed:error", {
|
|
431
|
+
bubbles: true,
|
|
432
|
+
composed: true,
|
|
433
|
+
detail: { code: error.code, message: error.message }
|
|
434
|
+
})
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
#showStatus() {
|
|
438
|
+
const v = this.getAttribute("show-status");
|
|
439
|
+
return v !== "false";
|
|
440
|
+
}
|
|
441
|
+
#showMute() {
|
|
442
|
+
const v = this.getAttribute("show-mute");
|
|
443
|
+
return v !== "false";
|
|
444
|
+
}
|
|
445
|
+
#render() {
|
|
446
|
+
if (!this.#root) return;
|
|
447
|
+
const copy = ui.resolveCopy(runtimeForCopy(this.#locale));
|
|
448
|
+
const ui$1 = this.uiState;
|
|
449
|
+
const inCall = ui$1 === "ringing" || ui$1 === "connected";
|
|
450
|
+
const labelAttr = this.getAttribute("label");
|
|
451
|
+
const buttonLabel = inCall ? copy.hangup : labelAttr ?? copy.launchDefault;
|
|
452
|
+
const statusText = ui$1 === "error" && this.#error ? ui.friendlyErrorMessage(copy, this.#error.code, this.#error.message) : statusLabel(copy, ui$1, this.#status);
|
|
453
|
+
const tone = ui$1 === "error" ? "error" : ui$1 === "connected" ? "ok" : void 0;
|
|
454
|
+
this.#root.replaceChildren();
|
|
455
|
+
const btn = document.createElement("button");
|
|
456
|
+
btn.type = "button";
|
|
457
|
+
btn.className = "ce-el-button";
|
|
458
|
+
btn.dataset.state = ui$1;
|
|
459
|
+
btn.disabled = this.#busy;
|
|
460
|
+
if (this.#busy) btn.setAttribute("aria-busy", "true");
|
|
461
|
+
btn.textContent = buttonLabel;
|
|
462
|
+
btn.addEventListener("click", () => {
|
|
463
|
+
void this.#onMainClick();
|
|
464
|
+
});
|
|
465
|
+
this.#root.appendChild(btn);
|
|
466
|
+
if (this.#showMute() && inCall) {
|
|
467
|
+
const muteBtn = document.createElement("button");
|
|
468
|
+
muteBtn.type = "button";
|
|
469
|
+
muteBtn.className = "ce-el-mute";
|
|
470
|
+
muteBtn.disabled = this.#busy;
|
|
471
|
+
muteBtn.textContent = this.#muted ? copy.unmute : copy.mute;
|
|
472
|
+
muteBtn.addEventListener("click", () => {
|
|
473
|
+
void this.#onMuteClick();
|
|
474
|
+
});
|
|
475
|
+
this.#root.appendChild(muteBtn);
|
|
476
|
+
}
|
|
477
|
+
if (this.#showStatus()) {
|
|
478
|
+
const statusEl = document.createElement("p");
|
|
479
|
+
statusEl.className = "ce-el-status";
|
|
480
|
+
statusEl.dataset.state = ui$1;
|
|
481
|
+
if (tone) statusEl.dataset.tone = tone;
|
|
482
|
+
statusEl.textContent = statusText;
|
|
483
|
+
this.#root.appendChild(statusEl);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async #onMainClick() {
|
|
487
|
+
if (this.#busy) return;
|
|
488
|
+
this.#busy = true;
|
|
489
|
+
this.#render();
|
|
490
|
+
try {
|
|
491
|
+
const ui = this.uiState;
|
|
492
|
+
if (ui === "ringing" || ui === "connected") {
|
|
493
|
+
await this.#end();
|
|
494
|
+
} else {
|
|
495
|
+
await this.#start();
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
} finally {
|
|
499
|
+
this.#busy = false;
|
|
500
|
+
this.#render();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async #onMuteClick() {
|
|
504
|
+
if (this.#busy) return;
|
|
505
|
+
this.#busy = true;
|
|
506
|
+
this.#render();
|
|
507
|
+
try {
|
|
508
|
+
await this.setMuted(!this.#muted);
|
|
509
|
+
} catch {
|
|
510
|
+
} finally {
|
|
511
|
+
this.#busy = false;
|
|
512
|
+
this.#render();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
function defineCallEmbedButton(tagName = TAG) {
|
|
517
|
+
if (typeof customElements === "undefined") {
|
|
518
|
+
return CallEmbedButtonElement;
|
|
519
|
+
}
|
|
520
|
+
if (!customElements.get(tagName)) {
|
|
521
|
+
customElements.define(tagName, CallEmbedButtonElement);
|
|
522
|
+
}
|
|
523
|
+
return CallEmbedButtonElement;
|
|
524
|
+
}
|
|
525
|
+
defineCallEmbedButton();
|
|
526
|
+
|
|
527
|
+
Object.defineProperty(exports, "DEFAULT_HOSTED_EMBED_ORIGIN", {
|
|
528
|
+
enumerable: true,
|
|
529
|
+
get: function () { return sdk.DEFAULT_HOSTED_EMBED_ORIGIN; }
|
|
530
|
+
});
|
|
531
|
+
Object.defineProperty(exports, "DEFAULT_HOSTED_EMBED_URL", {
|
|
532
|
+
enumerable: true,
|
|
533
|
+
get: function () { return sdk.DEFAULT_HOSTED_EMBED_URL; }
|
|
534
|
+
});
|
|
535
|
+
Object.defineProperty(exports, "enCopy", {
|
|
536
|
+
enumerable: true,
|
|
537
|
+
get: function () { return ui.enCopy; }
|
|
538
|
+
});
|
|
539
|
+
Object.defineProperty(exports, "friendlyErrorMessage", {
|
|
540
|
+
enumerable: true,
|
|
541
|
+
get: function () { return ui.friendlyErrorMessage; }
|
|
542
|
+
});
|
|
543
|
+
Object.defineProperty(exports, "resolveCopy", {
|
|
544
|
+
enumerable: true,
|
|
545
|
+
get: function () { return ui.resolveCopy; }
|
|
546
|
+
});
|
|
547
|
+
Object.defineProperty(exports, "zhTwCopy", {
|
|
548
|
+
enumerable: true,
|
|
549
|
+
get: function () { return ui.zhTwCopy; }
|
|
550
|
+
});
|
|
551
|
+
exports.CALLEMBED_BUTTON_TAG = TAG;
|
|
552
|
+
exports.CallEmbedButtonElement = CallEmbedButtonElement;
|
|
553
|
+
exports.defineCallEmbedButton = defineCallEmbedButton;
|
|
554
|
+
exports.statusLabel = statusLabel;
|
|
555
|
+
exports.toUiState = toUiState;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CallStatus, ActiveCall } from '@callembed/sdk';
|
|
2
|
+
export { ActiveCall, CallStatus, DEFAULT_HOSTED_EMBED_ORIGIN, DEFAULT_HOSTED_EMBED_URL, InitOptions, Locale, StartCallOptions } from '@callembed/sdk';
|
|
3
|
+
import { PanelCopy } from '@callembed/sdk/ui';
|
|
4
|
+
export { PanelCopy, enCopy, friendlyErrorMessage, resolveCopy, zhTwCopy } from '@callembed/sdk/ui';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simplified UI lifecycle for `<callembed-button>`.
|
|
8
|
+
* Maps onto SDK {@link CallStatus}:
|
|
9
|
+
* - `connecting` | `ringing` → `ringing`
|
|
10
|
+
* - `answered` → `connected`
|
|
11
|
+
*/
|
|
12
|
+
type CallEmbedUiState = "idle" | "ringing" | "connected" | "ended" | "error";
|
|
13
|
+
declare function toUiState(status: CallStatus | "idle"): CallEmbedUiState;
|
|
14
|
+
interface CallEmbedErrorState {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare const TAG = "callembed-button";
|
|
20
|
+
/**
|
|
21
|
+
* Framework-agnostic `<callembed-button>` custom element.
|
|
22
|
+
*
|
|
23
|
+
* Attributes: `publishable-key`, `api-base-url`, `locale` (`en` | `zh-TW`),
|
|
24
|
+
* `queue`, `label`, `show-status`, `show-mute`, `branding`, `debug`.
|
|
25
|
+
*
|
|
26
|
+
* Events: `callembed:state` (detail: `{ uiState, status, callId, muted }`),
|
|
27
|
+
* `callembed:error` (detail: `{ code, message }`).
|
|
28
|
+
*
|
|
29
|
+
* Hangs up on `disconnectedCallback` and when `publishable-key` / `api-base-url` change.
|
|
30
|
+
*/
|
|
31
|
+
declare class CallEmbedButtonElement extends HTMLElement {
|
|
32
|
+
#private;
|
|
33
|
+
static get observedAttributes(): string[];
|
|
34
|
+
get uiState(): CallEmbedUiState;
|
|
35
|
+
get callStatus(): CallStatus | "idle";
|
|
36
|
+
get callId(): string | null;
|
|
37
|
+
get muted(): boolean;
|
|
38
|
+
get error(): CallEmbedErrorState | null;
|
|
39
|
+
connectedCallback(): void;
|
|
40
|
+
disconnectedCallback(): void;
|
|
41
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
42
|
+
/** Start a queue call (customer-service ring → agent answers). */
|
|
43
|
+
start(): Promise<ActiveCall>;
|
|
44
|
+
/** Hang up the active call (idempotent). */
|
|
45
|
+
end(): Promise<void>;
|
|
46
|
+
setMuted(muted: boolean): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Registers `<callembed-button>` when `customElements` is available.
|
|
50
|
+
* Safe to import under SSR (Node / Nuxt): no-op when `customElements` is missing.
|
|
51
|
+
*/
|
|
52
|
+
declare function defineCallEmbedButton(tagName?: string): typeof CallEmbedButtonElement;
|
|
53
|
+
|
|
54
|
+
declare function statusLabel(copy: PanelCopy, uiState: CallEmbedUiState, sdkStatus: string): string;
|
|
55
|
+
|
|
56
|
+
export { TAG as CALLEMBED_BUTTON_TAG, CallEmbedButtonElement, type CallEmbedErrorState, type CallEmbedUiState, defineCallEmbedButton, statusLabel, toUiState };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CallStatus, ActiveCall } from '@callembed/sdk';
|
|
2
|
+
export { ActiveCall, CallStatus, DEFAULT_HOSTED_EMBED_ORIGIN, DEFAULT_HOSTED_EMBED_URL, InitOptions, Locale, StartCallOptions } from '@callembed/sdk';
|
|
3
|
+
import { PanelCopy } from '@callembed/sdk/ui';
|
|
4
|
+
export { PanelCopy, enCopy, friendlyErrorMessage, resolveCopy, zhTwCopy } from '@callembed/sdk/ui';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simplified UI lifecycle for `<callembed-button>`.
|
|
8
|
+
* Maps onto SDK {@link CallStatus}:
|
|
9
|
+
* - `connecting` | `ringing` → `ringing`
|
|
10
|
+
* - `answered` → `connected`
|
|
11
|
+
*/
|
|
12
|
+
type CallEmbedUiState = "idle" | "ringing" | "connected" | "ended" | "error";
|
|
13
|
+
declare function toUiState(status: CallStatus | "idle"): CallEmbedUiState;
|
|
14
|
+
interface CallEmbedErrorState {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare const TAG = "callembed-button";
|
|
20
|
+
/**
|
|
21
|
+
* Framework-agnostic `<callembed-button>` custom element.
|
|
22
|
+
*
|
|
23
|
+
* Attributes: `publishable-key`, `api-base-url`, `locale` (`en` | `zh-TW`),
|
|
24
|
+
* `queue`, `label`, `show-status`, `show-mute`, `branding`, `debug`.
|
|
25
|
+
*
|
|
26
|
+
* Events: `callembed:state` (detail: `{ uiState, status, callId, muted }`),
|
|
27
|
+
* `callembed:error` (detail: `{ code, message }`).
|
|
28
|
+
*
|
|
29
|
+
* Hangs up on `disconnectedCallback` and when `publishable-key` / `api-base-url` change.
|
|
30
|
+
*/
|
|
31
|
+
declare class CallEmbedButtonElement extends HTMLElement {
|
|
32
|
+
#private;
|
|
33
|
+
static get observedAttributes(): string[];
|
|
34
|
+
get uiState(): CallEmbedUiState;
|
|
35
|
+
get callStatus(): CallStatus | "idle";
|
|
36
|
+
get callId(): string | null;
|
|
37
|
+
get muted(): boolean;
|
|
38
|
+
get error(): CallEmbedErrorState | null;
|
|
39
|
+
connectedCallback(): void;
|
|
40
|
+
disconnectedCallback(): void;
|
|
41
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
42
|
+
/** Start a queue call (customer-service ring → agent answers). */
|
|
43
|
+
start(): Promise<ActiveCall>;
|
|
44
|
+
/** Hang up the active call (idempotent). */
|
|
45
|
+
end(): Promise<void>;
|
|
46
|
+
setMuted(muted: boolean): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Registers `<callembed-button>` when `customElements` is available.
|
|
50
|
+
* Safe to import under SSR (Node / Nuxt): no-op when `customElements` is missing.
|
|
51
|
+
*/
|
|
52
|
+
declare function defineCallEmbedButton(tagName?: string): typeof CallEmbedButtonElement;
|
|
53
|
+
|
|
54
|
+
declare function statusLabel(copy: PanelCopy, uiState: CallEmbedUiState, sdkStatus: string): string;
|
|
55
|
+
|
|
56
|
+
export { TAG as CALLEMBED_BUTTON_TAG, CallEmbedButtonElement, type CallEmbedErrorState, type CallEmbedUiState, defineCallEmbedButton, statusLabel, toUiState };
|