@bravophone/webphone 0.1.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.
@@ -0,0 +1,917 @@
1
+ const K = 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"', be = `<svg viewBox="0 0 24 24" fill="none" ${K} aria-hidden="true">
2
+ <path class="bp-i-body" d="M15.6 12.8a11 11 0 0 1-4.4-4.4l1-1a1.6 1.6 0 0 0 .3-1.7c-.2-.7-.4-1.4-.5-2.2A1.6 1.6 0 0 0 10.4 2H7.9a1.6 1.6 0 0 0-1.6 1.8 15.8 15.8 0 0 0 2.4 6.9 15.6 15.6 0 0 0 4.8 4.8 15.8 15.8 0 0 0 6.9 2.4 1.6 1.6 0 0 0 1.7-1.6v-2.5a1.6 1.6 0 0 0-1.4-1.6c-.8-.1-1.5-.3-2.2-.5a1.6 1.6 0 0 0-1.7.4z"/>
3
+ <path class="bp-i-wave bp-i-wave-1" d="M14.5 6.2a4 4 0 0 1 3.3 3.3"/>
4
+ <path class="bp-i-wave bp-i-wave-2" d="M14.1 2.6a7.6 7.6 0 0 1 7.3 7.3"/>
5
+ </svg>`, ue = `<svg viewBox="0 0 24 24" fill="none" ${K} aria-hidden="true">
6
+ <path class="bp-i-bar bp-i-bar-1" d="M4 10v4"/>
7
+ <path class="bp-i-bar bp-i-bar-2" d="M8.7 6.5v11"/>
8
+ <path class="bp-i-bar bp-i-bar-3" d="M12 3.5v17"/>
9
+ <path class="bp-i-bar bp-i-bar-4" d="M15.3 6.5v11"/>
10
+ <path class="bp-i-bar bp-i-bar-5" d="M20 10v4"/>
11
+ </svg>`, ge = `<svg viewBox="0 0 24 24" fill="none" ${K} aria-hidden="true">
12
+ <path class="bp-i-body" d="M4 13v-1a8 8 0 0 1 16 0v1"/>
13
+ <path class="bp-i-body" d="M20 14.5v2a3.5 3.5 0 0 1-3.5 3.5H13"/>
14
+ <rect class="bp-i-cup" x="1.6" y="12.6" width="4.4" height="6.4" rx="1.8"/>
15
+ <rect class="bp-i-cup" x="18" y="12.6" width="4.4" height="6.4" rx="1.8"/>
16
+ </svg>`, me = `<svg viewBox="0 0 24 24" fill="none" ${K} aria-hidden="true">
17
+ <path class="bp-i-body" d="M21 11.5a8.4 8.4 0 0 1-9 8.4 9 9 0 0 1-2.9-.5L3 21.5l1.5-4.6A8.4 8.4 0 0 1 12 3.1a8.4 8.4 0 0 1 9 8.4z"/>
18
+ <path class="bp-i-hand" d="M14.6 14.4a6.5 6.5 0 0 1-3.2-3.2l.7-.7a1 1 0 0 0 .2-1.1 6 6 0 0 1-.3-1.2 1 1 0 0 0-1-.8H9.6a1 1 0 0 0-1 1.1 9.6 9.6 0 0 0 4.8 7.2 1 1 0 0 0 1.4-.9v-1.2a1 1 0 0 0-.8-1z"/>
19
+ </svg>`, fe = `<svg viewBox="0 0 10 16" width="10" height="16" fill="currentColor" aria-hidden="true">
20
+ <circle cx="2.6" cy="2.6" r="1.35"/><circle cx="7.4" cy="2.6" r="1.35"/>
21
+ <circle cx="2.6" cy="8" r="1.35"/><circle cx="7.4" cy="8" r="1.35"/>
22
+ <circle cx="2.6" cy="13.4" r="1.35"/><circle cx="7.4" cy="13.4" r="1.35"/>
23
+ </svg>`, j = {
24
+ "phone-waves": be,
25
+ waveform: ue,
26
+ headset: ge,
27
+ "chat-phone": me
28
+ }, Q = "phone-waves", xe = `
29
+ .bp-launcher-icon svg { width: 100%; height: 100%; display: block; overflow: visible; }
30
+
31
+ /* Ponto de status no canto do ícone — verde registrado, cinza sem linha. */
32
+ .bp-launcher-icon::after {
33
+ content: '';
34
+ position: absolute;
35
+ right: -1px; bottom: -1px;
36
+ width: 7px; height: 7px;
37
+ border-radius: 50%;
38
+ background: #94a3b8;
39
+ box-shadow: 0 0 0 2px #6c5ce7;
40
+ transition: background .2s ease;
41
+ }
42
+ .bp-launcher[data-state="ready"] .bp-launcher-icon::after { background: #22c55e; }
43
+ .bp-launcher[data-state="error"] .bp-launcher-icon::after { background: #ef4444; }
44
+ /* Tocando ou em chamada, o ponto sai: a animacao ja comunica, e ele sujaria. */
45
+ .bp-launcher[data-state="ringing"] .bp-launcher-icon::after,
46
+ .bp-launcher[data-state="incall"] .bp-launcher-icon::after { display: none; }
47
+
48
+ /* --- ondas do handset: crescem de dentro para fora --- */
49
+ .bp-i-wave { opacity: .45; transform-origin: 12px 12px; }
50
+ .bp-launcher[data-state="ready"] .bp-i-wave { opacity: .75; }
51
+ @keyframes bp-i-emit {
52
+ 0% { opacity: 0; transform: scale(.6); }
53
+ 40% { opacity: 1; }
54
+ 100% { opacity: 0; transform: scale(1.15); }
55
+ }
56
+ .bp-launcher[data-state="ringing"] .bp-i-wave,
57
+ .bp-launcher[data-state="incall"] .bp-i-wave {
58
+ animation: bp-i-emit 1.4s ease-out infinite;
59
+ }
60
+ .bp-launcher[data-state="ringing"] .bp-i-wave-2,
61
+ .bp-launcher[data-state="incall"] .bp-i-wave-2 { animation-delay: .35s; }
62
+
63
+ /* --- handset balanca quando toca, como um telefone de verdade --- */
64
+ @keyframes bp-i-shake {
65
+ 0%, 100% { transform: rotate(0deg); }
66
+ 15%, 45% { transform: rotate(-13deg); }
67
+ 30%, 60% { transform: rotate(13deg); }
68
+ 75% { transform: rotate(0deg); }
69
+ }
70
+ .bp-launcher[data-state="ringing"] .bp-i-body,
71
+ .bp-launcher[data-state="ringing"] .bp-i-hand {
72
+ transform-origin: 12px 12px;
73
+ animation: bp-i-shake 1s ease-in-out infinite;
74
+ }
75
+
76
+ /* --- equalizador: barras dancam so em chamada --- */
77
+ .bp-i-bar { transform-origin: center; transition: transform .25s ease; }
78
+ .bp-launcher[data-state="incall"] .bp-i-bar {
79
+ animation: bp-i-dance .9s ease-in-out infinite;
80
+ }
81
+ @keyframes bp-i-dance {
82
+ 0%, 100% { transform: scaleY(.45); }
83
+ 50% { transform: scaleY(1); }
84
+ }
85
+ .bp-launcher[data-state="incall"] .bp-i-bar-2 { animation-delay: .12s }
86
+ .bp-launcher[data-state="incall"] .bp-i-bar-3 { animation-delay: .24s }
87
+ .bp-launcher[data-state="incall"] .bp-i-bar-4 { animation-delay: .36s }
88
+ .bp-launcher[data-state="incall"] .bp-i-bar-5 { animation-delay: .48s }
89
+
90
+ /* Hover: o icone da uma leve subida, so para o botao parecer vivo. */
91
+ .bp-launcher:hover .bp-launcher-icon { transform: translateY(-1px); }
92
+ .bp-launcher-icon { transition: transform .16s ease; }
93
+
94
+ @media (prefers-reduced-motion: reduce) {
95
+ .bp-i-wave, .bp-i-body, .bp-i-hand, .bp-i-bar { animation: none !important; }
96
+ .bp-launcher-icon { transition: none; }
97
+ }
98
+ `, we = `
99
+ :host { all: initial; }
100
+ * { box-sizing: border-box; }
101
+
102
+ .bp-root {
103
+ position: fixed;
104
+ z-index: 2147483000;
105
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
106
+ display: flex;
107
+ flex-direction: column;
108
+ background: #1e1e2d;
109
+ border-radius: 12px;
110
+ box-shadow: 0 12px 40px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.06);
111
+ overflow: hidden;
112
+ will-change: transform;
113
+ }
114
+ .bp-root[hidden] { display: none !important; }
115
+ .bp-root { transition: left .16s ease, top .16s ease, width .16s ease, height .16s ease; }
116
+ .bp-root.bp-dragging,
117
+ .bp-root.bp-resizing { transition: none; user-select: none; }
118
+ .bp-root.bp-minimized .bp-body,
119
+ .bp-root.bp-minimized /* ---- modo sem moldura (frame:'none', padrao) ----
120
+ A UI do webphone ocupa 100% da janela. Os controles ficam sobrepostos,
121
+ invisiveis ate o hover, e nunca empurram nem redimensionam o conteudo. */
122
+ .bp-root.bp-chromeless .bp-header { display: none; }
123
+ .bp-root.bp-chromeless .bp-overlay { display: flex; }
124
+
125
+ .bp-overlay {
126
+ position: absolute;
127
+ top: 6px; right: 6px;
128
+ display: none;
129
+ gap: 2px;
130
+ padding: 3px;
131
+ border-radius: 8px;
132
+ background: rgba(10,10,16,.72);
133
+ backdrop-filter: blur(6px);
134
+ opacity: 0;
135
+ transition: opacity .14s ease;
136
+ z-index: 3;
137
+ pointer-events: none;
138
+ }
139
+ .bp-root.bp-chromeless:hover .bp-overlay,
140
+ .bp-root.bp-chromeless:focus-within .bp-overlay { opacity: 1; pointer-events: auto; }
141
+ .bp-root.bp-dragging .bp-overlay { opacity: 0; pointer-events: none; }
142
+ .bp-overlay .bp-btn { width: 22px; height: 22px; font-size: 13px; color: #cbd5e1; }
143
+
144
+ /* Sem barra de titulo o arraste vem de dentro do iframe: o cursor tambem. */
145
+ .bp-root.bp-chromeless.bp-dragging { cursor: grabbing; }
146
+
147
+ .bp-resize { display: none; }
148
+ .bp-root.bp-minimized { height: auto !important; }
149
+
150
+ .bp-header {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 8px;
154
+ height: 36px;
155
+ padding: 0 6px 0 12px;
156
+ background: #15151f;
157
+ color: #e6e6f0;
158
+ cursor: grab;
159
+ flex: 0 0 auto;
160
+ touch-action: none;
161
+ }
162
+ .bp-header:active { cursor: grabbing; }
163
+ .bp-title {
164
+ flex: 1 1 auto;
165
+ font-size: 12px;
166
+ font-weight: 600;
167
+ letter-spacing: .3px;
168
+ white-space: nowrap;
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ }
172
+ .bp-status {
173
+ width: 8px; height: 8px; border-radius: 50%;
174
+ background: #6b7280; flex: 0 0 auto;
175
+ }
176
+ .bp-status[data-state="ready"] { background: #22c55e; }
177
+ .bp-status[data-state="ringing"] { background: #f59e0b; animation: bp-pulse 1s infinite; }
178
+ .bp-status[data-state="incall"] { background: #22c55e; animation: bp-pulse 2s infinite; }
179
+ .bp-status[data-state="error"] { background: #ef4444; }
180
+ @keyframes bp-pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
181
+
182
+ .bp-btn {
183
+ width: 24px; height: 24px;
184
+ display: grid; place-items: center;
185
+ border: 0; border-radius: 6px;
186
+ background: transparent; color: #9ca3af;
187
+ cursor: pointer; font-size: 15px; line-height: 1;
188
+ flex: 0 0 auto; padding: 0;
189
+ }
190
+ .bp-btn:hover { background: rgba(255,255,255,.08); color: #fff; }
191
+ .bp-btn-close:hover { background: #dc2626; color: #fff; }
192
+
193
+ .bp-body { flex: 1 1 auto; position: relative; min-height: 0; }
194
+ .bp-frame {
195
+ width: 100%; height: 100%;
196
+ border: 0; display: block; background: #1e1e2d;
197
+ }
198
+ /* Durante o arraste o iframe precisa parar de capturar o ponteiro,
199
+ senão o mousemove é engolido pelo documento interno e o drag "trava". */
200
+ .bp-root.bp-dragging .bp-frame,
201
+ .bp-root.bp-resizing .bp-frame { pointer-events: none; }
202
+
203
+ /* ---- alcas de redimensionamento (8 bordas/cantos) ----
204
+ Faixas invisiveis por fora da borda: nao cobrem a UI do webphone e ainda
205
+ assim dao uma area de clique confortavel. */
206
+ .bp-h {
207
+ position: absolute;
208
+ z-index: 4;
209
+ touch-action: none;
210
+ }
211
+ .bp-h-n { top: -4px; left: 10px; right: 10px; height: 8px; cursor: ns-resize; }
212
+ .bp-h-s { bottom: -4px; left: 10px; right: 10px; height: 8px; cursor: ns-resize; }
213
+ .bp-h-w { left: -4px; top: 10px; bottom: 10px; width: 8px; cursor: ew-resize; }
214
+ .bp-h-e { right: -4px; top: 10px; bottom: 10px; width: 8px; cursor: ew-resize; }
215
+ .bp-h-nw { top: -4px; left: -4px; width: 14px; height: 14px; cursor: nwse-resize; }
216
+ .bp-h-ne { top: -4px; right: -4px; width: 14px; height: 14px; cursor: nesw-resize; }
217
+ .bp-h-sw { bottom: -4px; left: -4px; width: 14px; height: 14px; cursor: nesw-resize; }
218
+ .bp-h-se { bottom: -4px; right: -4px; width: 14px; height: 14px; cursor: nwse-resize; }
219
+
220
+ /* Marcador visivel so no canto inferior direito, como affordance. */
221
+ .bp-h-se::after {
222
+ content: ''; position: absolute; right: 3px; bottom: 3px;
223
+ width: 9px; height: 9px;
224
+ background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,.3) 50%);
225
+ }
226
+
227
+ /* Uma janela docada nao se redimensiona pelas bordas coladas na viewport. */
228
+ .bp-root.bp-docked { border-radius: 0; }
229
+
230
+ /* ---- previa de encaixe (docking) ----
231
+ Fantasma que mostra onde a janela vai parar antes de soltar. */
232
+ .bp-preview {
233
+ position: fixed;
234
+ z-index: 2147482998;
235
+ border-radius: 10px;
236
+ background: rgba(37,99,235,.18);
237
+ border: 2px solid rgba(96,165,250,.85);
238
+ box-shadow: 0 0 0 1px rgba(0,0,0,.25), inset 0 0 40px rgba(96,165,250,.15);
239
+ pointer-events: none;
240
+ transition: left .12s ease, top .12s ease, width .12s ease, height .12s ease;
241
+ }
242
+ .bp-preview[hidden] { display: none !important; }
243
+
244
+ .bp-launcher {
245
+ position: fixed;
246
+ top: 50%;
247
+ z-index: 2147482999;
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0;
251
+ height: 48px;
252
+ padding: 0 11px;
253
+ background: #6c5ce7;
254
+ color: #fff;
255
+ border: 0;
256
+ cursor: pointer;
257
+ box-shadow: 0 4px 16px rgba(76,58,190,.42), 0 0 0 1px rgba(255,255,255,.08) inset;
258
+ transition: padding .16s ease, background .16s ease, box-shadow .16s ease;
259
+ touch-action: none;
260
+ user-select: none;
261
+ -webkit-user-select: none;
262
+ }
263
+ .bp-launcher[hidden] { display: none !important; }
264
+
265
+ /* Colada na borda: os cantos arredondados sao so os que ficam para dentro. */
266
+ .bp-launcher[data-side="right"] { border-radius: 14px 0 0 14px; }
267
+ .bp-launcher[data-side="left"] { border-radius: 0 14px 14px 0; }
268
+
269
+ .bp-launcher:hover,
270
+ .bp-launcher:focus-visible {
271
+ background: #5b4bd6;
272
+ box-shadow: 0 6px 22px rgba(76,58,190,.55), 0 0 0 1px rgba(255,255,255,.12) inset;
273
+ }
274
+ .bp-launcher:focus-visible { outline: 2px solid #c7d2fe; outline-offset: 2px; }
275
+ .bp-launcher.bp-launcher-dragging { cursor: grabbing; transition: none; }
276
+
277
+ /* Alca de arraste: os pontos ficam sempre com largura fixa e somem por
278
+ opacidade + margem negativa. Animar a largura cortaria o SVG no meio. */
279
+ .bp-launcher-grip {
280
+ width: 10px;
281
+ height: 16px;
282
+ flex: 0 0 auto;
283
+ opacity: 0;
284
+ color: rgba(255,255,255,.9);
285
+ cursor: grab;
286
+ transition: opacity .16s ease, margin .16s ease;
287
+ }
288
+ .bp-launcher-grip svg { display: block; }
289
+ .bp-launcher[data-side="right"] .bp-launcher-grip { order: 0; margin-right: -10px; }
290
+ .bp-launcher[data-side="left"] .bp-launcher-grip { order: 2; margin-left: -10px; }
291
+
292
+ .bp-launcher:hover .bp-launcher-grip,
293
+ .bp-launcher:focus-visible .bp-launcher-grip,
294
+ .bp-launcher.bp-launcher-dragging .bp-launcher-grip { opacity: 1; }
295
+ .bp-launcher[data-side="right"]:hover .bp-launcher-grip,
296
+ .bp-launcher[data-side="right"]:focus-visible .bp-launcher-grip,
297
+ .bp-launcher[data-side="right"].bp-launcher-dragging .bp-launcher-grip { margin-right: 7px; }
298
+ .bp-launcher[data-side="left"]:hover .bp-launcher-grip,
299
+ .bp-launcher[data-side="left"]:focus-visible .bp-launcher-grip,
300
+ .bp-launcher[data-side="left"].bp-launcher-dragging .bp-launcher-grip { margin-left: 7px; }
301
+
302
+ .bp-launcher-icon {
303
+ position: relative;
304
+ order: 1;
305
+ display: grid;
306
+ place-items: center;
307
+ width: 22px;
308
+ height: 22px;
309
+ flex: 0 0 auto;
310
+ pointer-events: none;
311
+ }
312
+ .bp-launcher-icon svg { width: 100%; height: 100%; display: block; }
313
+
314
+ .bp-launcher .bp-badge {
315
+ position: absolute;
316
+ top: -5px;
317
+ min-width: 18px;
318
+ height: 18px;
319
+ padding: 0 5px;
320
+ border-radius: 9px;
321
+ background: #ef4444;
322
+ color: #fff;
323
+ font: 700 11px/18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
324
+ text-align: center;
325
+ display: none;
326
+ box-shadow: 0 0 0 2px #6c5ce7;
327
+ }
328
+ .bp-launcher[data-side="right"] .bp-badge { left: -6px; }
329
+ .bp-launcher[data-side="left"] .bp-badge { right: -6px; }
330
+ .bp-launcher[data-badge]:not([data-badge=""]) .bp-badge { display: block; }
331
+
332
+ /* Chamada entrante: a aba pulsa para chamar atencao mesmo fechada. */
333
+ @keyframes bp-launcher-ring {
334
+ 0%, 100% { box-shadow: 0 4px 16px rgba(76,58,190,.42); }
335
+ 50% { box-shadow: 0 4px 16px rgba(239,68,68,.75), 0 0 0 4px rgba(239,68,68,.25); }
336
+ }
337
+ .bp-launcher[data-badge]:not([data-badge=""]) { animation: bp-launcher-ring 1.1s infinite; }
338
+
339
+ @media (prefers-reduced-motion: reduce) {
340
+ .bp-launcher, .bp-launcher-grip { transition: none; }
341
+ .bp-launcher[data-badge]:not([data-badge=""]) { animation: none; }
342
+ }
343
+
344
+ ${xe}
345
+ `, pe = "bravophone:widget:geometry", q = 28, J = 32;
346
+ function ye(e) {
347
+ try {
348
+ const a = localStorage.getItem(pe);
349
+ if (!a) return { ...e };
350
+ const i = JSON.parse(a);
351
+ return i.dock ? { ...e, ...i, ...F(i.dock, i) } : { ...e, ...i };
352
+ } catch (a) {
353
+ return { ...e };
354
+ }
355
+ }
356
+ function X(e) {
357
+ try {
358
+ localStorage.setItem(pe, JSON.stringify(e));
359
+ } catch (a) {
360
+ }
361
+ }
362
+ function se(e) {
363
+ return {
364
+ minWidth: e.minWidth,
365
+ minHeight: e.minHeight,
366
+ // Teto relativo à viewport: numa tela larga o histórico de chamadas pode
367
+ // usar o espaço que precisa, sem um limite fixo apertando.
368
+ maxWidth: Math.max(e.minWidth, Math.min(e.maxWidth, window.innerWidth)),
369
+ maxHeight: Math.max(e.minHeight, Math.min(e.maxHeight, window.innerHeight))
370
+ };
371
+ }
372
+ function R(e) {
373
+ const a = Math.max(0, window.innerWidth - e.width), i = Math.max(0, window.innerHeight - 40);
374
+ return {
375
+ ...e,
376
+ x: Math.min(Math.max(0, e.x), a),
377
+ y: Math.min(Math.max(0, e.y), i)
378
+ };
379
+ }
380
+ function F(e, a) {
381
+ const i = window.innerWidth, r = window.innerHeight, o = Math.round(i / 2), s = Math.round(r / 2), u = Math.min(a.width, i);
382
+ switch (e) {
383
+ case "left":
384
+ return { x: 0, y: 0, width: u, height: r };
385
+ case "right":
386
+ return { x: i - u, y: 0, width: u, height: r };
387
+ case "left-half":
388
+ return { x: 0, y: 0, width: o, height: r };
389
+ case "right-half":
390
+ return { x: i - o, y: 0, width: o, height: r };
391
+ case "top":
392
+ return { x: a.x, y: 0, width: u, height: s };
393
+ case "bottom":
394
+ return { x: a.x, y: r - s, width: u, height: s };
395
+ case "top-half":
396
+ return { x: 0, y: 0, width: i, height: s };
397
+ case "bottom-half":
398
+ return { x: 0, y: r - s, width: i, height: s };
399
+ case "max":
400
+ return { x: 0, y: 0, width: i, height: r };
401
+ default:
402
+ return { x: a.x, y: a.y, width: a.width, height: a.height };
403
+ }
404
+ }
405
+ function ve(e, a, i) {
406
+ const r = window.innerWidth, o = window.innerHeight, s = e <= q, u = e >= r - q, f = a <= q, t = a >= o - q;
407
+ return (f || t) && s ? "left-half" : (f || t) && u ? "right-half" : f ? i || "max" : t ? "bottom-half" : s ? "left" : u ? "right" : null;
408
+ }
409
+ function ke({ root: e, handle: a, handles: i, preview: r, geometry: o, limits: s, topDock: u, onChange: f }) {
410
+ let t = { ...o }, g = null, p = "", d = null, h = null, w = null, y = null;
411
+ const z = () => {
412
+ e.style.left = `${t.x}px`, e.style.top = `${t.y}px`, e.style.width = `${t.width}px`, e.style.height = `${t.height}px`, e.classList.toggle("bp-docked", !!t.dock), f == null || f(t);
413
+ }, C = (n) => {
414
+ if (r) {
415
+ if (!n) {
416
+ r.hidden = !0;
417
+ return;
418
+ }
419
+ r.hidden = !1, r.style.left = `${n.x}px`, r.style.top = `${n.y}px`, r.style.width = `${n.width}px`, r.style.height = `${n.height}px`;
420
+ }
421
+ }, D = (n, b) => {
422
+ const l = se(s);
423
+ let { x: S, y: m, width: k, height: H } = d;
424
+ p.includes("e") && (k = d.width + n), p.includes("w") && (k = d.width - n), p.includes("s") && (H = d.height + b), p.includes("n") && (H = d.height - b), k = Math.min(l.maxWidth, Math.max(l.minWidth, k)), H = Math.min(l.maxHeight, Math.max(l.minHeight, H)), p.includes("w") && (S = d.x + (d.width - k)), p.includes("n") && (m = d.y + (d.height - H)), p.includes("e") && Math.abs(S + k - window.innerWidth) <= J && (k = window.innerWidth - S), p.includes("w") && Math.abs(S) <= J && (k += S, S = 0), p.includes("s") && Math.abs(m + H - window.innerHeight) <= J && (H = window.innerHeight - m), p.includes("n") && Math.abs(m) <= J && (H += m, m = 0), t = { ...t, x: S, y: m, width: k, height: H, dock: null }, z();
425
+ }, $ = (n, b, l, S) => {
426
+ if (t.dock) {
427
+ const m = y || { width: s.width, height: s.height }, k = h.x / Math.max(1, t.width);
428
+ t = { ...t, ...m, dock: null }, h = { x: Math.round(m.width * k), y: Math.min(h.y, 32) }, d = {
429
+ ...d,
430
+ x: l - h.x,
431
+ y: S - h.y,
432
+ px: d.px + n,
433
+ py: d.py + b
434
+ }, n = 0, b = 0;
435
+ }
436
+ t = R({ ...t, x: d.x + n, y: d.y + b }), z(), w = ve(l, S, u), C(w ? F(w, t) : null);
437
+ }, O = () => {
438
+ w && (y = y || { width: t.width, height: t.height }, t = { ...t, ...F(w, t), dock: w }, z());
439
+ }, P = (n, b) => (l) => {
440
+ l.button === 0 && (n === "drag" && l.target.closest(".bp-btn") || (g = n, p = b || "", d = { px: l.clientX, py: l.clientY, ...t }, h = { x: l.clientX - t.x, y: l.clientY - t.y }, t.dock || (y = { width: t.width, height: t.height }), e.classList.add(n === "drag" ? "bp-dragging" : "bp-resizing"), l.currentTarget.setPointerCapture(l.pointerId), l.preventDefault()));
441
+ }, U = (n) => {
442
+ if (!g || !d) return;
443
+ const b = n.clientX - d.px, l = n.clientY - d.py;
444
+ g === "drag" ? $(b, l, n.clientX, n.clientY) : D(b, l);
445
+ }, B = (n) => {
446
+ if (g) {
447
+ e.classList.remove("bp-dragging", "bp-resizing"), C(null), g === "drag" && O(), g = null, p = "", d = null, w = null;
448
+ try {
449
+ n.currentTarget.releasePointerCapture(n.pointerId);
450
+ } catch (b) {
451
+ }
452
+ X(t);
453
+ }
454
+ };
455
+ a.addEventListener("pointerdown", P("drag")), a.addEventListener("pointermove", U), a.addEventListener("pointerup", B), a.addEventListener("pointercancel", B), (i || []).forEach(({ el: n, dir: b }) => {
456
+ n.addEventListener("pointerdown", P("resize", b)), n.addEventListener("pointermove", U), n.addEventListener("pointerup", B), n.addEventListener("pointercancel", B);
457
+ });
458
+ const V = () => {
459
+ t = t.dock ? { ...t, ...F(t.dock, t) } : R(t), z();
460
+ };
461
+ window.addEventListener("resize", V);
462
+ const G = {
463
+ start(n) {
464
+ var b, l;
465
+ g = "drag", d = { px: n.x, py: n.y, ...t }, h = { x: (b = n.cx) != null ? b : t.width / 2, y: (l = n.cy) != null ? l : 20 }, t.dock || (y = { width: t.width, height: t.height }), e.classList.add("bp-dragging");
466
+ },
467
+ move(n) {
468
+ if (g !== "drag" || !d) return;
469
+ const b = n.x - d.px, l = n.y - d.py;
470
+ $(b, l, d.x + b + h.x, d.y + l + h.y);
471
+ },
472
+ end() {
473
+ g === "drag" && (e.classList.remove("bp-dragging"), C(null), O(), g = null, d = null, w = null, X(t));
474
+ }
475
+ };
476
+ return z(), {
477
+ external: G,
478
+ get geometry() {
479
+ return { ...t };
480
+ },
481
+ set(n) {
482
+ const b = se(s), l = { ...t, ...n, dock: null };
483
+ l.width = Math.min(b.maxWidth, Math.max(b.minWidth, l.width)), l.height = Math.min(b.maxHeight, Math.max(b.minHeight, l.height)), t = R(l), z(), X(t);
484
+ },
485
+ /** 'left' | 'right' | 'left-half' | 'right-half' | 'max' | 'float' */
486
+ dock(n) {
487
+ if (!n || n === "float") {
488
+ const b = y || { width: s.width, height: s.height };
489
+ t = R({ ...t, ...b, dock: null });
490
+ } else
491
+ t.dock || (y = { width: t.width, height: t.height }), t = { ...t, ...F(n, t), dock: n };
492
+ z(), X(t);
493
+ },
494
+ destroy() {
495
+ window.removeEventListener("resize", V);
496
+ }
497
+ };
498
+ }
499
+ const de = "bravophone/v1";
500
+ function Me({ frame: e, origin: a, onEvent: i }) {
501
+ const r = /* @__PURE__ */ new Map();
502
+ let o = 0, s = !1;
503
+ const u = [], f = (g) => {
504
+ var d;
505
+ if (g.origin !== a || g.source !== e.contentWindow) return;
506
+ const p = g.data;
507
+ if (!(!p || p.protocol !== de)) {
508
+ if (p.type === "ready") {
509
+ for (s = !0; u.length; ) u.shift()();
510
+ i == null || i("ready", (d = p.payload) != null ? d : {});
511
+ return;
512
+ }
513
+ if (p.type === "reply") {
514
+ const h = r.get(p.id);
515
+ if (!h) return;
516
+ r.delete(p.id), clearTimeout(h.timer), p.error ? h.reject(new Error(p.error)) : h.resolve(p.payload);
517
+ return;
518
+ }
519
+ p.type === "event" && (i == null || i(p.name, p.payload));
520
+ }
521
+ };
522
+ window.addEventListener("message", f);
523
+ function t(g, p, { timeout: d = 15e3 } = {}) {
524
+ return new Promise((h, w) => {
525
+ const y = `${Date.now()}-${++o}`, z = () => {
526
+ var D;
527
+ const C = setTimeout(() => {
528
+ r.delete(y), w(new Error(`Bravophone: timeout no comando "${g}"`));
529
+ }, d);
530
+ r.set(y, { resolve: h, reject: w, timer: C }), (D = e.contentWindow) == null || D.postMessage(
531
+ { protocol: de, type: "command", id: y, command: g, payload: p },
532
+ a
533
+ );
534
+ };
535
+ s ? z() : u.push(z);
536
+ });
537
+ }
538
+ return {
539
+ call: t,
540
+ get ready() {
541
+ return s;
542
+ },
543
+ destroy() {
544
+ window.removeEventListener("message", f), r.forEach(({ reject: g, timer: p }) => {
545
+ clearTimeout(p), g(new Error("Bravophone: widget destruído"));
546
+ }), r.clear();
547
+ }
548
+ };
549
+ }
550
+ const he = "bravophone:launcher", ze = 4, ee = 8;
551
+ function Le(e) {
552
+ try {
553
+ const a = localStorage.getItem(he);
554
+ return a ? { ...e, ...JSON.parse(a) } : { ...e };
555
+ } catch (a) {
556
+ return { ...e };
557
+ }
558
+ }
559
+ function te(e) {
560
+ try {
561
+ localStorage.setItem(he, JSON.stringify(e));
562
+ } catch (a) {
563
+ }
564
+ }
565
+ function Y(e, a) {
566
+ const i = Math.max(ee, window.innerHeight - a - ee);
567
+ return Math.min(Math.max(ee, e), i);
568
+ }
569
+ function Ee({ el: e, side: a, y: i, onOpen: r }) {
570
+ let o = { side: a, y: Y(i, e.offsetHeight || 48) }, s = null, u = !1;
571
+ const f = () => {
572
+ e.dataset.side = o.side, e.style.top = `${o.y}px`, e.style.right = o.side === "right" ? "0px" : "auto", e.style.left = o.side === "left" ? "0px" : "auto";
573
+ }, t = (h) => {
574
+ h.button === 0 && (s = { py: h.clientY, y: o.y }, u = !1, e.setPointerCapture(h.pointerId));
575
+ }, g = (h) => {
576
+ if (!s) return;
577
+ const w = h.clientY - s.py;
578
+ !u && Math.abs(w) < ze || (u || (u = !0, e.classList.add("bp-launcher-dragging")), o.y = Y(s.y + w, e.offsetHeight), f());
579
+ }, p = (h) => {
580
+ if (!s) return;
581
+ const w = u;
582
+ s = null, u = !1, e.classList.remove("bp-launcher-dragging");
583
+ try {
584
+ e.releasePointerCapture(h.pointerId);
585
+ } catch (y) {
586
+ }
587
+ w ? te(o) : r == null || r();
588
+ };
589
+ e.addEventListener("pointerdown", t), e.addEventListener("pointermove", g), e.addEventListener("pointerup", p), e.addEventListener("pointercancel", p), e.addEventListener("keydown", (h) => {
590
+ (h.key === "Enter" || h.key === " ") && (h.preventDefault(), r == null || r());
591
+ });
592
+ const d = () => {
593
+ o.y = Y(o.y, e.offsetHeight), f();
594
+ };
595
+ return window.addEventListener("resize", d), f(), {
596
+ get state() {
597
+ return { ...o };
598
+ },
599
+ setSide(h) {
600
+ o.side = h, f(), te(o);
601
+ },
602
+ moveTo(h) {
603
+ o.y = Y(h, e.offsetHeight), f(), te(o);
604
+ },
605
+ destroy() {
606
+ window.removeEventListener("resize", d);
607
+ }
608
+ };
609
+ }
610
+ const ce = {
611
+ // 380x640 é a geometria da janela nativa da extensão — o layout do
612
+ // webphone é calibrado para ela, então é o ponto de partida aqui também.
613
+ width: 380,
614
+ height: 640,
615
+ minWidth: 320,
616
+ minHeight: 420,
617
+ // Tetos generosos: o limite real é a viewport (ver limitsFor). O histórico
618
+ // de chamadas fica ilegível espremido em 380px, então dá para alargar muito.
619
+ maxWidth: 1600,
620
+ maxHeight: 1400,
621
+ margin: 24
622
+ };
623
+ function v(e, a = {}, i = []) {
624
+ const r = document.createElement(e);
625
+ for (const [o, s] of Object.entries(a))
626
+ o === "class" ? r.className = s : o === "text" ? r.textContent = s : s != null && r.setAttribute(o, s);
627
+ return i.forEach((o) => r.appendChild(o)), r;
628
+ }
629
+ function Se(e) {
630
+ const {
631
+ hostUrl: a,
632
+ token: i,
633
+ position: r = "bottom-right",
634
+ open: o = !1,
635
+ launcher: s = !0,
636
+ title: u = "BRAVOPHONE",
637
+ // 'none' preserva 100% da UI do webphone: sem barra de titulo, o arraste
638
+ // vem de dentro do iframe. 'bar' adiciona a barra com titulo e controles.
639
+ frame: f = "none",
640
+ // O que arrastar até a borda de cima faz. 'max' é o gesto universal;
641
+ // troque para 'top-half' se preferir as metades verticais no topo.
642
+ dockTop: t = "max",
643
+ // Lado da viewport em que a aba de abertura fica colada.
644
+ launcherSide: g = "right",
645
+ // Desenho da aba: 'phone-waves' | 'waveform' | 'headset' | 'chat-phone'
646
+ launcherIcon: p = Q,
647
+ emit: d
648
+ } = e, h = new URL(a), w = h.origin, y = v("div", { "data-bravophone": "root" });
649
+ y.style.cssText = "all:initial;position:static";
650
+ const z = y.attachShadow({ mode: "open" });
651
+ z.appendChild(v("style", { text: we }));
652
+ const C = (() => {
653
+ var oe;
654
+ const { width: c, height: E, margin: I } = ce, N = window.innerWidth - c - I, ie = window.innerHeight - E - I, re = {
655
+ "bottom-right": { x: N, y: ie },
656
+ "bottom-left": { x: I, y: ie },
657
+ "top-right": { x: N, y: I },
658
+ "top-left": { x: I, y: I }
659
+ };
660
+ return R({ ...(oe = re[r]) != null ? oe : re["bottom-right"], width: c, height: E });
661
+ })(), D = R(ye(C)), $ = v("span", { class: "bp-status", "data-state": "connecting" }), O = v("button", { class: "bp-btn bp-btn-min", title: "Minimizar", text: "—" }), P = v("button", { class: "bp-btn bp-btn-close", title: "Fechar", text: "×" }), U = v("div", { class: "bp-header" }, [
662
+ $,
663
+ v("span", { class: "bp-title", text: u }),
664
+ O,
665
+ P
666
+ ]), B = v("iframe", {
667
+ class: "bp-frame",
668
+ src: He(h, { token: i, embed: "1", parent: location.origin }),
669
+ allow: "microphone; autoplay; clipboard-write; speaker-selection",
670
+ title: "Webphone BRAVOPHONE"
671
+ }), G = ["n", "s", "w", "e", "nw", "ne", "sw", "se"].map((c) => ({
672
+ el: v("div", { class: `bp-h bp-h-${c}` }),
673
+ dir: c
674
+ })), n = v("div", { class: "bp-preview" });
675
+ n.hidden = !0;
676
+ const b = v("button", { class: "bp-btn bp-btn-close", title: "Fechar", text: "×" }), l = v("div", { class: "bp-overlay" }, [b]), S = v("div", { class: "bp-body" }, [B, l]), m = v("div", { class: "bp-root" }, [U, S, ...G.map((c) => c.el)]);
677
+ f !== "bar" && m.classList.add("bp-chromeless"), m.hidden = !o;
678
+ const k = v("span", { class: "bp-launcher-icon" });
679
+ k.innerHTML = j[p] || j[Q];
680
+ const H = v("span", { class: "bp-launcher-grip", "aria-hidden": "true" });
681
+ H.innerHTML = fe;
682
+ const L = v("div", {
683
+ class: "bp-launcher",
684
+ role: "button",
685
+ tabindex: "0",
686
+ title: "Abrir webphone",
687
+ "aria-label": "Abrir webphone"
688
+ }, [
689
+ H,
690
+ k,
691
+ v("span", { class: "bp-badge" })
692
+ ]);
693
+ L.dataset.state = "connecting", L.hidden = !s || o, z.append(n, m, L), document.body.appendChild(y);
694
+ const ae = Le({
695
+ side: g,
696
+ y: Math.round(window.innerHeight / 2 - 24)
697
+ }), ne = Ee({
698
+ el: L,
699
+ side: ae.side,
700
+ y: Y(ae.y, 48),
701
+ onOpen: () => T.show()
702
+ }), W = ke({
703
+ root: m,
704
+ handle: U,
705
+ handles: G,
706
+ preview: n,
707
+ geometry: D,
708
+ limits: ce,
709
+ topDock: t,
710
+ onChange: (c) => d("resize", { width: c.width, height: c.height, dock: c.dock || null })
711
+ }), Z = Me({
712
+ frame: B,
713
+ origin: w,
714
+ onEvent: (c, E) => {
715
+ var I;
716
+ if (c === "ready" && ($.dataset.state = "ready", L.dataset.state = "ready", i && Z.call("auth", { token: i }).catch(() => {
717
+ })), c === "state") {
718
+ const N = (I = E == null ? void 0 : E.state) != null ? I : "ready";
719
+ $.dataset.state = N, L.dataset.state = N;
720
+ }
721
+ if (c === "call:incoming" && (T.show(), L.dataset.badge = "1", L.dataset.state = "ringing"), c === "call:answered" && (L.dataset.state = "incall"), (c === "call:ended" || c === "call:failed") && (delete L.dataset.badge, L.dataset.state = "ready"), c === "drag:start") {
722
+ W.external.start(E);
723
+ return;
724
+ }
725
+ if (c === "drag:move") {
726
+ W.external.move(E);
727
+ return;
728
+ }
729
+ if (c === "drag:end") {
730
+ W.external.end();
731
+ return;
732
+ }
733
+ d(c, E);
734
+ }
735
+ });
736
+ let _ = !1;
737
+ const T = {
738
+ el: y,
739
+ bridge: Z,
740
+ show() {
741
+ m.hidden = !1, L.hidden = !0, d("open");
742
+ },
743
+ hide() {
744
+ m.hidden = !0, s && (L.hidden = !1), d("close");
745
+ },
746
+ toggle() {
747
+ m.hidden ? T.show() : T.hide();
748
+ },
749
+ get isOpen() {
750
+ return !m.hidden;
751
+ },
752
+ minimize(c) {
753
+ if (f !== "bar") {
754
+ T.hide();
755
+ return;
756
+ }
757
+ _ = c != null ? c : !_, m.classList.toggle("bp-minimized", _), O.textContent = _ ? "▢" : "—";
758
+ },
759
+ move(c, E) {
760
+ W.set({ x: c, y: E });
761
+ },
762
+ resize(c, E) {
763
+ W.set({ width: c, height: E });
764
+ },
765
+ dock(c) {
766
+ W.dock(c);
767
+ },
768
+ setLauncherSide(c) {
769
+ ne.setSide(c);
770
+ },
771
+ setLauncherIcon(c) {
772
+ k.innerHTML = j[c] || j[Q];
773
+ },
774
+ get geometry() {
775
+ return W.geometry;
776
+ },
777
+ destroy() {
778
+ Z.destroy(), W.destroy(), ne.destroy(), n.remove(), y.remove();
779
+ }
780
+ };
781
+ return P.addEventListener("click", () => T.hide()), O.addEventListener("click", () => T.minimize()), b.addEventListener("click", () => T.hide()), T;
782
+ }
783
+ function He(e, a) {
784
+ const i = new URL(e.href);
785
+ for (const [r, o] of Object.entries(a))
786
+ o != null && o !== "" && i.searchParams.set(r, o);
787
+ return a.token && (i.searchParams.delete("token"), i.hash = `token=${encodeURIComponent(a.token)}`), i.href;
788
+ }
789
+ const Te = "https://webphone.bravophone.com/embed/";
790
+ let M = null;
791
+ const A = /* @__PURE__ */ new Map();
792
+ function Ie(e, a) {
793
+ var i, r;
794
+ (i = A.get(e)) == null || i.forEach((o) => {
795
+ try {
796
+ o(a);
797
+ } catch (s) {
798
+ console.error("[Bravophone] listener falhou:", s);
799
+ }
800
+ }), (r = A.get("*")) == null || r.forEach((o) => {
801
+ try {
802
+ o({ event: e, payload: a });
803
+ } catch (s) {
804
+ console.error("[Bravophone] listener falhou:", s);
805
+ }
806
+ });
807
+ }
808
+ function x() {
809
+ if (!M) throw new Error("Bravophone: chame Bravophone.init() antes.");
810
+ return M;
811
+ }
812
+ const le = {
813
+ /**
814
+ * Monta o webphone na página.
815
+ * @param {object} opts
816
+ * @param {string} [opts.hostUrl] Origem do webphone hospedado.
817
+ * @param {string} [opts.token] Token de sessão do usuário (SSO do integrador).
818
+ * @param {'bottom-right'|'bottom-left'|'top-right'|'top-left'} [opts.position]
819
+ * @param {boolean} [opts.open] Abrir já visível (padrão: false, só o launcher).
820
+ * @param {boolean} [opts.launcher] Exibir o botão flutuante (padrão: true).
821
+ */
822
+ init(e = {}) {
823
+ if (M) return M;
824
+ if (typeof window == "undefined" || !document.body)
825
+ throw new Error("Bravophone: init() precisa rodar no browser, após o <body> existir.");
826
+ return location.protocol !== "https:" && location.hostname !== "localhost" && console.warn("[Bravophone] Contexto inseguro: o microfone exige HTTPS ou localhost."), M = Se({ hostUrl: Te, ...e, emit: Ie }), M;
827
+ },
828
+ // ---- Controle da janela ----
829
+ show() {
830
+ x().show();
831
+ },
832
+ hide() {
833
+ x().hide();
834
+ },
835
+ toggle() {
836
+ x().toggle();
837
+ },
838
+ minimize(e) {
839
+ x().minimize(e);
840
+ },
841
+ move(e, a) {
842
+ x().move(e, a);
843
+ },
844
+ resize(e, a) {
845
+ x().resize(e, a);
846
+ },
847
+ /** 'left' | 'right' | 'left-half' | 'right-half' | 'max' | 'float' */
848
+ dock(e) {
849
+ x().dock(e);
850
+ },
851
+ /** Move a aba de abertura para o outro lado da viewport. */
852
+ setLauncherSide(e) {
853
+ x().setLauncherSide(e);
854
+ },
855
+ /** 'phone-waves' | 'waveform' | 'headset' | 'chat-phone' */
856
+ setLauncherIcon(e) {
857
+ x().setLauncherIcon(e);
858
+ },
859
+ get isOpen() {
860
+ return M ? M.isOpen : !1;
861
+ },
862
+ get geometry() {
863
+ return M ? M.geometry : null;
864
+ },
865
+ // ---- Telefonia ----
866
+ /** Disca um número. Aceita formato livre; a normalização é a mesma da extensão. */
867
+ call(e, a) {
868
+ return x().bridge.call("call", { number: e, meta: a });
869
+ },
870
+ hangup() {
871
+ return x().bridge.call("hangup");
872
+ },
873
+ answer() {
874
+ return x().bridge.call("answer");
875
+ },
876
+ /** Alterna o mudo — o bundle só expõe toggle, não aceita estado alvo. */
877
+ mute() {
878
+ return x().bridge.call("mute");
879
+ },
880
+ /** Alterna a espera. Também toggle. */
881
+ hold() {
882
+ return x().bridge.call("hold");
883
+ },
884
+ sendDTMF(e) {
885
+ return x().bridge.call("dtmf", { tone: e });
886
+ },
887
+ transfer(e) {
888
+ return x().bridge.call("transfer", { to: e });
889
+ },
890
+ getStatus() {
891
+ return x().bridge.call("status");
892
+ },
893
+ setAuth(e) {
894
+ return x().bridge.call("auth", { token: e });
895
+ },
896
+ logout() {
897
+ return x().bridge.call("logout");
898
+ },
899
+ // ---- Eventos ----
900
+ /** Eventos: ready, state, call:dialing, call:incoming, call:answered,
901
+ * call:ended, call:failed, resize, open, close, error. */
902
+ on(e, a) {
903
+ return A.has(e) || A.set(e, /* @__PURE__ */ new Set()), A.get(e).add(a), () => le.off(e, a);
904
+ },
905
+ off(e, a) {
906
+ var i;
907
+ (i = A.get(e)) == null || i.delete(a);
908
+ },
909
+ destroy() {
910
+ M == null || M.destroy(), M = null, A.clear();
911
+ },
912
+ version: "0.1.0"
913
+ };
914
+ typeof window != "undefined" && (window.Bravophone = le);
915
+ export {
916
+ le as default
917
+ };