@signalwire/web-components 4.0.0-beta.2 → 4.0.0-beta.4

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 (75) hide show
  1. package/README.md +3 -1
  2. package/dist/components/audio-level.d.ts +106 -0
  3. package/dist/components/audio-level.d.ts.map +1 -0
  4. package/dist/components/audio-level.js +203 -0
  5. package/dist/components/audio-level.js.map +1 -0
  6. package/dist/components/call-controls.d.ts +163 -0
  7. package/dist/components/call-controls.d.ts.map +1 -0
  8. package/dist/components/call-controls.js +606 -0
  9. package/dist/components/call-controls.js.map +1 -0
  10. package/dist/components/call-media.d.ts +114 -0
  11. package/dist/components/call-media.d.ts.map +1 -0
  12. package/dist/components/call-media.js +215 -0
  13. package/dist/components/call-media.js.map +1 -0
  14. package/dist/components/call-status.d.ts +71 -0
  15. package/dist/components/call-status.d.ts.map +1 -0
  16. package/dist/components/call-status.js +251 -0
  17. package/dist/components/call-status.js.map +1 -0
  18. package/dist/components/click-to-call.d.ts +123 -0
  19. package/dist/components/click-to-call.d.ts.map +1 -0
  20. package/dist/components/click-to-call.js +428 -0
  21. package/dist/components/click-to-call.js.map +1 -0
  22. package/dist/components/device-selector.d.ts +250 -0
  23. package/dist/components/device-selector.d.ts.map +1 -0
  24. package/dist/components/device-selector.js +685 -0
  25. package/dist/components/device-selector.js.map +1 -0
  26. package/dist/components/dialpad.d.ts +60 -0
  27. package/dist/components/dialpad.d.ts.map +1 -0
  28. package/dist/components/dialpad.js +372 -0
  29. package/dist/components/dialpad.js.map +1 -0
  30. package/dist/components/directory.d.ts +103 -0
  31. package/dist/components/directory.d.ts.map +1 -0
  32. package/dist/components/directory.js +503 -0
  33. package/dist/components/directory.js.map +1 -0
  34. package/dist/components/example-button.d.ts +20 -0
  35. package/dist/components/example-button.d.ts.map +1 -0
  36. package/dist/components/example-button.js +74 -0
  37. package/dist/components/example-button.js.map +1 -0
  38. package/dist/components/participant-controls.d.ts +94 -0
  39. package/dist/components/participant-controls.d.ts.map +1 -0
  40. package/dist/components/participant-controls.js +468 -0
  41. package/dist/components/participant-controls.js.map +1 -0
  42. package/dist/components/participants.d.ts +116 -0
  43. package/dist/components/participants.d.ts.map +1 -0
  44. package/dist/components/participants.js +394 -0
  45. package/dist/components/participants.js.map +1 -0
  46. package/dist/components/self-media.d.ts +78 -0
  47. package/dist/components/self-media.d.ts.map +1 -0
  48. package/dist/components/self-media.js +128 -0
  49. package/dist/components/self-media.js.map +1 -0
  50. package/dist/context/call-context.d.ts +13 -0
  51. package/dist/context/call-context.d.ts.map +1 -0
  52. package/dist/context/call-context.js +6 -0
  53. package/dist/context/call-context.js.map +1 -0
  54. package/dist/context/index.d.ts +2 -0
  55. package/dist/context/index.d.ts.map +1 -0
  56. package/dist/index.d.ts +29 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +39 -5645
  59. package/dist/index.js.map +1 -1
  60. package/dist/react.d.ts +101 -0
  61. package/dist/types/index.d.ts +67 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +12 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/utils/debounce.d.ts +10 -0
  66. package/dist/utils/debounce.d.ts.map +1 -0
  67. package/dist/utils/debounce.js +13 -0
  68. package/dist/utils/debounce.js.map +1 -0
  69. package/dist/utils/index.d.ts +3 -0
  70. package/dist/utils/index.d.ts.map +1 -0
  71. package/dist/utils/video.d.ts +34 -0
  72. package/dist/utils/video.d.ts.map +1 -0
  73. package/dist/utils/video.js +26 -0
  74. package/dist/utils/video.js.map +1 -0
  75. package/package.json +70 -3
@@ -0,0 +1,685 @@
1
+ import { LitElement as _, html as a, nothing as b, css as f } from "lit";
2
+ import { property as m, state as n, customElement as g } from "lit/decorators.js";
3
+ var S = Object.defineProperty, y = Object.getOwnPropertyDescriptor, c = (e, i, r, s) => {
4
+ for (var t = s > 1 ? void 0 : s ? y(i, r) : i, l = e.length - 1, d; l >= 0; l--)
5
+ (d = e[l]) && (t = (s ? d(i, r, t) : d(t)) || t);
6
+ return s && t && S(i, r, t), t;
7
+ };
8
+ let o = class extends _ {
9
+ constructor() {
10
+ super(...arguments), this.showPreview = !1, this._audioInputDevices = [], this._videoInputDevices = [], this._audioOutputDevices = [], this._selectedAudioInput = null, this._selectedVideoInput = null, this._selectedAudioOutput = null, this._videoPreviewStream = null, this._audioPreviewStream = null, this._isTestingAudio = !1, this._isComponentVisible = !1, this._isInViewport = !1, this._isDocumentVisible = !0, this._isCSSVisible = !0, this.subscriptions = [], this._boundHandleDocumentVisibility = this.handleDocumentVisibilityChange.bind(this), this._isInitializingPreviews = !1;
11
+ }
12
+ /**
13
+ * Lifecycle: Component connected to DOM
14
+ */
15
+ connectedCallback() {
16
+ super.connectedCallback(), this.setupSubscriptions(), this.setupVisibilityObservers();
17
+ }
18
+ /**
19
+ * Lifecycle: React to property changes
20
+ */
21
+ updated(e) {
22
+ super.updated(e), e.has("deviceController") && (this.cleanupSubscriptions(), this.setupSubscriptions()), e.has("showPreview") && (this.showPreview && this._isComponentVisible ? this.initializePreviews() : this.showPreview || (this.cleanupVideoPreviewStream(), this.cleanupAudioPreviewStream()));
23
+ }
24
+ /**
25
+ * Lifecycle: Component disconnected from DOM
26
+ */
27
+ disconnectedCallback() {
28
+ super.disconnectedCallback(), this.cleanupSubscriptions(), this.cleanupVisibilityObservers(), this.cleanupVideoPreviewStream(), this.cleanupAudioPreviewStream(), this.stopTestAudio();
29
+ }
30
+ /**
31
+ * Setup all visibility observers (IntersectionObserver, document visibility, CSS polling)
32
+ */
33
+ setupVisibilityObservers() {
34
+ this.cleanupVisibilityObservers(), this._intersectionObserver = new IntersectionObserver(
35
+ (e) => {
36
+ const i = e[0];
37
+ i && (this._isInViewport = i.isIntersecting, this.updateVisibilityState());
38
+ },
39
+ { threshold: 0 }
40
+ ), this._intersectionObserver.observe(this), this._isDocumentVisible = document.visibilityState === "visible", document.addEventListener("visibilitychange", this._boundHandleDocumentVisibility), this._isCSSVisible = this.checkCSSVisibility(), this._cssVisibilityCheckInterval = setInterval(() => {
41
+ const e = this._isCSSVisible;
42
+ this._isCSSVisible = this.checkCSSVisibility(), e !== this._isCSSVisible && this.updateVisibilityState();
43
+ }, o.CSS_VISIBILITY_CHECK_INTERVAL_MS);
44
+ }
45
+ /**
46
+ * Cleanup all visibility observers
47
+ */
48
+ cleanupVisibilityObservers() {
49
+ this._intersectionObserver && (this._intersectionObserver.disconnect(), this._intersectionObserver = void 0), document.removeEventListener("visibilitychange", this._boundHandleDocumentVisibility), this._cssVisibilityCheckInterval && (clearInterval(this._cssVisibilityCheckInterval), this._cssVisibilityCheckInterval = void 0);
50
+ }
51
+ /**
52
+ * Handle document visibility change (tab switching)
53
+ */
54
+ handleDocumentVisibilityChange() {
55
+ this._isDocumentVisible = document.visibilityState === "visible", this.updateVisibilityState();
56
+ }
57
+ /**
58
+ * Check if the component is visible via CSS (display, visibility, opacity)
59
+ * Traverses the DOM tree including shadow DOM boundaries
60
+ */
61
+ checkCSSVisibility() {
62
+ if (!this.isConnected)
63
+ return !1;
64
+ let e = this;
65
+ for (; e; ) {
66
+ const i = getComputedStyle(e);
67
+ if (i.display === "none" || i.visibility === "hidden" || parseFloat(i.opacity) === 0)
68
+ return !1;
69
+ if (e.parentElement)
70
+ e = e.parentElement;
71
+ else {
72
+ const r = e.getRootNode();
73
+ e = r instanceof ShadowRoot ? r.host : null;
74
+ }
75
+ }
76
+ return !0;
77
+ }
78
+ /**
79
+ * Update the combined visibility state and manage preview streams accordingly
80
+ */
81
+ updateVisibilityState() {
82
+ const e = this._isComponentVisible;
83
+ this._isComponentVisible = this._isInViewport && this._isDocumentVisible && this._isCSSVisible, e !== this._isComponentVisible && (this._isComponentVisible && this.showPreview ? this.initializePreviews() : this._isComponentVisible || (this.cleanupVideoPreviewStream(), this.cleanupAudioPreviewStream()));
84
+ }
85
+ /**
86
+ * Subscribe to device controller observables
87
+ */
88
+ setupSubscriptions() {
89
+ this.deviceController && (this.subscriptions.push(
90
+ this.deviceController.audioInputDevices$.subscribe((e) => {
91
+ this._audioInputDevices = e;
92
+ })
93
+ ), this.subscriptions.push(
94
+ this.deviceController.videoInputDevices$.subscribe((e) => {
95
+ this._videoInputDevices = e;
96
+ })
97
+ ), this.subscriptions.push(
98
+ this.deviceController.audioOutputDevices$.subscribe((e) => {
99
+ this._audioOutputDevices = e;
100
+ })
101
+ ), this.deviceController.selectedAudioInputDevice$ && this.subscriptions.push(
102
+ this.deviceController.selectedAudioInputDevice$.subscribe((e) => {
103
+ this._selectedAudioInput = e;
104
+ })
105
+ ), this.deviceController.selectedVideoInputDevice$ && this.subscriptions.push(
106
+ this.deviceController.selectedVideoInputDevice$.subscribe((e) => {
107
+ this._selectedVideoInput = e;
108
+ })
109
+ ), this.deviceController.selectedAudioOutputDevice$ && this.subscriptions.push(
110
+ this.deviceController.selectedAudioOutputDevice$.subscribe((e) => {
111
+ this._selectedAudioOutput = e;
112
+ })
113
+ ));
114
+ }
115
+ /**
116
+ * Cleanup all subscriptions
117
+ */
118
+ cleanupSubscriptions() {
119
+ this.subscriptions.forEach((e) => e.unsubscribe()), this.subscriptions = [];
120
+ }
121
+ /**
122
+ * Cleanup video preview stream
123
+ * Best practice from SDK: Pause video, clear srcObject, remove tracks from stream, then stop tracks
124
+ * @see https://webrtchacks.com/srcobject-intervention/
125
+ */
126
+ cleanupVideoPreviewStream() {
127
+ var i;
128
+ this._videoElement && (this._videoElement.pause(), this._videoElement.srcObject = null, this._videoElement = void 0);
129
+ const e = (i = this.shadowRoot) == null ? void 0 : i.querySelector("video");
130
+ e && (e.pause(), e.srcObject = null), this._videoPreviewStream && (this._videoPreviewStream.getTracks().forEach((s) => {
131
+ var t;
132
+ (t = this._videoPreviewStream) == null || t.removeTrack(s), s.stop();
133
+ }), this._videoPreviewStream = null);
134
+ }
135
+ /**
136
+ * Cleanup audio preview stream
137
+ * Best practice from SDK: Release audio components, remove tracks from stream, then stop tracks
138
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop
139
+ */
140
+ cleanupAudioPreviewStream() {
141
+ var i;
142
+ const e = (i = this.shadowRoot) == null ? void 0 : i.querySelectorAll("sw-audio-level");
143
+ e == null || e.forEach((r) => {
144
+ const s = r;
145
+ s.releaseResources ? s.releaseResources() : s.stream = void 0;
146
+ }), this._audioPreviewStream && (this._audioPreviewStream.getTracks().forEach((s) => {
147
+ var t;
148
+ (t = this._audioPreviewStream) == null || t.removeTrack(s), s.stop();
149
+ }), this._audioPreviewStream = null);
150
+ }
151
+ /**
152
+ * Get video preview stream from browser API
153
+ */
154
+ async getVideoPreviewStream(e) {
155
+ if (this.cleanupVideoPreviewStream(), !!this.showPreview)
156
+ try {
157
+ const i = {
158
+ video: e ? { deviceId: { exact: e } } : !0,
159
+ audio: !1
160
+ };
161
+ this._videoPreviewStream = await navigator.mediaDevices.getUserMedia(i), this.updateVideoElement();
162
+ } catch (i) {
163
+ console.warn("Failed to get video preview stream:", i), this._videoPreviewStream = null;
164
+ }
165
+ }
166
+ /**
167
+ * Get audio preview stream from browser API
168
+ */
169
+ async getAudioPreviewStream(e) {
170
+ if (this.cleanupAudioPreviewStream(), !!this.showPreview)
171
+ try {
172
+ const i = {
173
+ video: !1,
174
+ audio: e ? { deviceId: { exact: e } } : !0
175
+ };
176
+ this._audioPreviewStream = await navigator.mediaDevices.getUserMedia(i);
177
+ } catch (i) {
178
+ console.warn("Failed to get audio preview stream:", i), this._audioPreviewStream = null;
179
+ }
180
+ }
181
+ /**
182
+ * Update video element with current stream
183
+ */
184
+ updateVideoElement() {
185
+ this._videoElement && this._videoPreviewStream && (this._videoElement.srcObject = this._videoPreviewStream);
186
+ }
187
+ /**
188
+ * Initialize previews when show-preview is enabled
189
+ */
190
+ async initializePreviews() {
191
+ var e, i;
192
+ if (!(!this.showPreview || this._isInitializingPreviews)) {
193
+ this._isInitializingPreviews = !0;
194
+ try {
195
+ if (this._videoInputDevices.length > 0) {
196
+ const r = (e = this._selectedVideoInput) == null ? void 0 : e.deviceId;
197
+ await this.getVideoPreviewStream(r);
198
+ }
199
+ if (this._audioInputDevices.length > 0) {
200
+ const r = (i = this._selectedAudioInput) == null ? void 0 : i.deviceId;
201
+ await this.getAudioPreviewStream(r);
202
+ }
203
+ } finally {
204
+ this._isInitializingPreviews = !1;
205
+ }
206
+ }
207
+ }
208
+ /**
209
+ * Handle device selection change from dropdown
210
+ */
211
+ handleDeviceChange(e, i) {
212
+ var l, d, u, p, h, w;
213
+ if (!this.deviceController) return;
214
+ const s = e.target.value;
215
+ let t = null;
216
+ switch (i) {
217
+ case "microphone":
218
+ t = this._audioInputDevices.find((v) => v.deviceId === s) || null, this._selectedAudioInput = t, (d = (l = this.deviceController).selectAudioInputDevice) == null || d.call(l, t), this.showPreview && this.getAudioPreviewStream(s);
219
+ break;
220
+ case "camera":
221
+ t = this._videoInputDevices.find((v) => v.deviceId === s) || null, this._selectedVideoInput = t, (p = (u = this.deviceController).selectVideoInputDevice) == null || p.call(u, t), this.showPreview && this.getVideoPreviewStream(s);
222
+ break;
223
+ case "speaker":
224
+ t = this._audioOutputDevices.find((v) => v.deviceId === s) || null, this._selectedAudioOutput = t, (w = (h = this.deviceController).selectAudioOutputDevice) == null || w.call(h, t);
225
+ break;
226
+ }
227
+ t && this.dispatchEvent(
228
+ new CustomEvent("sw-device-change", {
229
+ detail: { device: t, deviceType: i },
230
+ bubbles: !0,
231
+ composed: !0
232
+ })
233
+ );
234
+ }
235
+ /**
236
+ * Test speaker by playing a test tone
237
+ */
238
+ async testSpeaker() {
239
+ if (this._isTestingAudio) {
240
+ this.stopTestAudio();
241
+ return;
242
+ }
243
+ try {
244
+ this._isTestingAudio = !0;
245
+ const e = new AudioContext(), i = e.createOscillator(), r = e.createGain();
246
+ i.type = "sine", i.frequency.value = 440, r.gain.value = 0.3, i.connect(r), r.connect(e.destination), i.start(), setTimeout(() => {
247
+ i.stop(), e.close(), this._isTestingAudio = !1;
248
+ }, 1e3), this.dispatchEvent(
249
+ new CustomEvent("sw-test-speaker", {
250
+ bubbles: !0,
251
+ composed: !0
252
+ })
253
+ );
254
+ } catch (e) {
255
+ console.error("Failed to play test audio:", e), this._isTestingAudio = !1;
256
+ }
257
+ }
258
+ /**
259
+ * Stop test audio
260
+ */
261
+ stopTestAudio() {
262
+ this._testAudioElement && (this._testAudioElement.pause(), this._testAudioElement = void 0), this._isTestingAudio = !1;
263
+ }
264
+ /**
265
+ * Render device icon
266
+ */
267
+ renderDeviceIcon(e) {
268
+ switch (e) {
269
+ case "microphone":
270
+ return a`<svg class="device-icon" viewBox="0 0 24 24" fill="currentColor">
271
+ <path
272
+ d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z"
273
+ />
274
+ </svg>`;
275
+ case "camera":
276
+ return a`<svg class="device-icon" viewBox="0 0 24 24" fill="currentColor">
277
+ <path
278
+ d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"
279
+ />
280
+ </svg>`;
281
+ case "speaker":
282
+ return a`<svg class="device-icon" viewBox="0 0 24 24" fill="currentColor">
283
+ <path
284
+ d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
285
+ />
286
+ </svg>`;
287
+ }
288
+ }
289
+ /**
290
+ * Render dropdown arrow icon
291
+ */
292
+ renderSelectArrow() {
293
+ return a`<svg class="select-arrow" viewBox="0 0 24 24" fill="currentColor">
294
+ <path d="M7 10l5 5 5-5z" />
295
+ </svg>`;
296
+ }
297
+ /**
298
+ * Render a device selection section
299
+ */
300
+ renderDeviceSection(e, i, r, s) {
301
+ return a`
302
+ <div class="device-section" part="device-section">
303
+ <label class="device-label" for="select-${e}">
304
+ ${this.renderDeviceIcon(e)}
305
+ <span>${i}</span>
306
+ </label>
307
+ ${r.length === 0 ? a`<div class="no-devices">No ${i.toLowerCase()} found</div>` : a`
308
+ <div class="device-select-wrapper">
309
+ <select
310
+ id="select-${e}"
311
+ class="device-select"
312
+ part="device-select"
313
+ aria-label="${i}"
314
+ .value=${(s == null ? void 0 : s.deviceId) || ""}
315
+ @change=${(t) => this.handleDeviceChange(t, e)}
316
+ >
317
+ ${r.map(
318
+ (t) => a`
319
+ <option
320
+ value="${t.deviceId}"
321
+ ?selected=${(s == null ? void 0 : s.deviceId) === t.deviceId}
322
+ >
323
+ ${t.label || `Device ${t.deviceId.slice(0, 8)}`}
324
+ </option>
325
+ `
326
+ )}
327
+ </select>
328
+ ${this.renderSelectArrow()}
329
+ </div>
330
+ `}
331
+ </div>
332
+ `;
333
+ }
334
+ /**
335
+ * Render video preview for camera section
336
+ */
337
+ renderVideoPreview() {
338
+ return !this.showPreview || this._videoInputDevices.length === 0 ? b : a`
339
+ <div class="device-preview">
340
+ ${this._videoPreviewStream ? a`
341
+ <div class="video-preview">
342
+ <video
343
+ autoplay
344
+ playsinline
345
+ muted
346
+ .srcObject=${this._videoPreviewStream}
347
+ @loadedmetadata=${(e) => {
348
+ const i = e.target;
349
+ this._videoElement = i, i.play().catch(() => {
350
+ });
351
+ }}
352
+ ></video>
353
+ </div>
354
+ ` : a` <div class="video-preview-placeholder">Click to enable camera preview</div> `}
355
+ </div>
356
+ `;
357
+ }
358
+ /**
359
+ * Render audio level preview for microphone section
360
+ */
361
+ renderAudioPreview() {
362
+ return !this.showPreview || this._audioInputDevices.length === 0 ? b : a`
363
+ <div class="device-preview">
364
+ <div class="audio-preview">
365
+ <span class="audio-preview-label">Level:</span>
366
+ <div class="audio-level-wrapper">
367
+ ${this._audioPreviewStream ? a`
368
+ <sw-audio-level
369
+ .stream=${this._audioPreviewStream}
370
+ bars="10"
371
+ orientation="horizontal"
372
+ maxSize="20"
373
+ ></sw-audio-level>
374
+ ` : a`
375
+ <span style="color: var(--sw-color-text-muted); font-size: 12px;"
376
+ >No audio input</span
377
+ >
378
+ `}
379
+ </div>
380
+ </div>
381
+ </div>
382
+ `;
383
+ }
384
+ /**
385
+ * Render the component
386
+ */
387
+ render() {
388
+ return a`
389
+ <div class="container" part="container">
390
+ ${this.renderDeviceSection(
391
+ "microphone",
392
+ "Microphone",
393
+ this._audioInputDevices,
394
+ this._selectedAudioInput
395
+ )}
396
+ ${this.renderAudioPreview()}
397
+ ${this.renderDeviceSection(
398
+ "camera",
399
+ "Camera",
400
+ this._videoInputDevices,
401
+ this._selectedVideoInput
402
+ )}
403
+ ${this.renderVideoPreview()}
404
+ ${this.renderDeviceSection(
405
+ "speaker",
406
+ "Speaker",
407
+ this._audioOutputDevices,
408
+ this._selectedAudioOutput
409
+ )}
410
+
411
+ <div class="device-section">
412
+ <button
413
+ class="test-speaker-btn"
414
+ @click=${this.testSpeaker}
415
+ ?disabled=${this._isTestingAudio}
416
+ aria-label="Test speaker"
417
+ >
418
+ ${this._isTestingAudio ? "Playing..." : "Test Speaker"}
419
+ </button>
420
+ </div>
421
+ </div>
422
+ `;
423
+ }
424
+ };
425
+ o.styles = f`
426
+ :host {
427
+ /* CSS Custom Properties for theming */
428
+ --sw-color-primary: #044cf6;
429
+ --sw-color-primary-hover: #0339c4;
430
+ --sw-color-background: #1a1a1a;
431
+ --sw-color-surface: #2a2a2a;
432
+ --sw-color-surface-hover: #3a3a3a;
433
+ --sw-color-text: #ffffff;
434
+ --sw-color-text-muted: #a0a0a0;
435
+ --sw-color-border: #404040;
436
+ --sw-color-success: #10b981;
437
+ --sw-border-radius: 8px;
438
+ --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
439
+ --sw-space-1: 4px;
440
+ --sw-space-2: 8px;
441
+ --sw-space-3: 12px;
442
+ --sw-space-4: 16px;
443
+ --sw-space-6: 24px;
444
+
445
+ display: block;
446
+ font-family: var(--sw-font-family);
447
+ color: var(--sw-color-text);
448
+ }
449
+
450
+ .container {
451
+ background: var(--sw-color-background);
452
+ border-radius: var(--sw-border-radius);
453
+ border: 1px solid var(--sw-color-border);
454
+ overflow: hidden;
455
+ min-width: 300px;
456
+ padding: var(--sw-space-4);
457
+ }
458
+
459
+ .device-section {
460
+ margin-bottom: var(--sw-space-4);
461
+ }
462
+
463
+ .device-section:last-of-type {
464
+ margin-bottom: 0;
465
+ }
466
+
467
+ .device-label {
468
+ display: flex;
469
+ align-items: center;
470
+ gap: var(--sw-space-2);
471
+ font-size: 14px;
472
+ font-weight: 500;
473
+ color: var(--sw-color-text);
474
+ margin-bottom: var(--sw-space-2);
475
+ }
476
+
477
+ .device-icon {
478
+ width: 18px;
479
+ height: 18px;
480
+ flex-shrink: 0;
481
+ color: var(--sw-color-text-muted);
482
+ }
483
+
484
+ .device-select-wrapper {
485
+ position: relative;
486
+ }
487
+
488
+ .device-select {
489
+ width: 100%;
490
+ padding: var(--sw-space-3) var(--sw-space-4);
491
+ padding-right: 36px;
492
+ background: var(--sw-color-surface);
493
+ border: 1px solid var(--sw-color-border);
494
+ border-radius: calc(var(--sw-border-radius) - 4px);
495
+ color: var(--sw-color-text);
496
+ font-size: 14px;
497
+ font-family: var(--sw-font-family);
498
+ cursor: pointer;
499
+ appearance: none;
500
+ -webkit-appearance: none;
501
+ -moz-appearance: none;
502
+ transition:
503
+ border-color 0.2s ease,
504
+ background-color 0.2s ease;
505
+ }
506
+
507
+ .device-select:hover {
508
+ background: var(--sw-color-surface-hover);
509
+ }
510
+
511
+ .device-select:focus {
512
+ outline: none;
513
+ border-color: var(--sw-color-primary);
514
+ box-shadow: 0 0 0 2px rgba(4, 76, 246, 0.2);
515
+ }
516
+
517
+ .device-select:disabled {
518
+ opacity: 0.5;
519
+ cursor: not-allowed;
520
+ }
521
+
522
+ .device-select option {
523
+ background: var(--sw-color-surface);
524
+ color: var(--sw-color-text);
525
+ padding: var(--sw-space-2);
526
+ }
527
+
528
+ .select-arrow {
529
+ position: absolute;
530
+ right: var(--sw-space-3);
531
+ top: 50%;
532
+ transform: translateY(-50%);
533
+ width: 16px;
534
+ height: 16px;
535
+ color: var(--sw-color-text-muted);
536
+ pointer-events: none;
537
+ }
538
+
539
+ .no-devices {
540
+ padding: var(--sw-space-3) var(--sw-space-4);
541
+ background: var(--sw-color-surface);
542
+ border: 1px solid var(--sw-color-border);
543
+ border-radius: calc(var(--sw-border-radius) - 4px);
544
+ color: var(--sw-color-text-muted);
545
+ font-size: 14px;
546
+ }
547
+
548
+ .device-preview {
549
+ margin-top: var(--sw-space-3);
550
+ }
551
+
552
+ .video-preview {
553
+ width: 100%;
554
+ aspect-ratio: 16/9;
555
+ background: #000;
556
+ border-radius: calc(var(--sw-border-radius) - 4px);
557
+ overflow: hidden;
558
+ }
559
+
560
+ .video-preview video {
561
+ width: 100%;
562
+ height: 100%;
563
+ object-fit: cover;
564
+ transform: scaleX(-1);
565
+ }
566
+
567
+ .video-preview-placeholder {
568
+ width: 100%;
569
+ aspect-ratio: 16/9;
570
+ background: var(--sw-color-surface);
571
+ border-radius: calc(var(--sw-border-radius) - 4px);
572
+ display: flex;
573
+ align-items: center;
574
+ justify-content: center;
575
+ color: var(--sw-color-text-muted);
576
+ font-size: 14px;
577
+ }
578
+
579
+ .audio-preview {
580
+ display: flex;
581
+ align-items: center;
582
+ gap: var(--sw-space-3);
583
+ padding: var(--sw-space-3);
584
+ background: var(--sw-color-surface);
585
+ border-radius: calc(var(--sw-border-radius) - 4px);
586
+ }
587
+
588
+ .audio-preview-label {
589
+ font-size: 12px;
590
+ color: var(--sw-color-text-muted);
591
+ flex-shrink: 0;
592
+ }
593
+
594
+ .audio-level-wrapper {
595
+ flex: 1;
596
+ display: flex;
597
+ justify-content: center;
598
+ }
599
+
600
+ .test-speaker-btn {
601
+ padding: var(--sw-space-2) var(--sw-space-4);
602
+ background: var(--sw-color-primary);
603
+ color: white;
604
+ border: none;
605
+ border-radius: calc(var(--sw-border-radius) - 4px);
606
+ font-size: 14px;
607
+ cursor: pointer;
608
+ transition: background-color 0.2s ease;
609
+ }
610
+
611
+ .test-speaker-btn:hover {
612
+ background: var(--sw-color-primary-hover);
613
+ }
614
+
615
+ .test-speaker-btn:focus {
616
+ outline: none;
617
+ box-shadow:
618
+ 0 0 0 2px var(--sw-color-background),
619
+ 0 0 0 4px var(--sw-color-primary);
620
+ }
621
+
622
+ .test-speaker-btn:disabled {
623
+ opacity: 0.5;
624
+ cursor: not-allowed;
625
+ }
626
+
627
+ /* Scrollbar styling for select on some browsers */
628
+ .device-select::-webkit-scrollbar {
629
+ width: 8px;
630
+ }
631
+
632
+ .device-select::-webkit-scrollbar-track {
633
+ background: var(--sw-color-background);
634
+ }
635
+
636
+ .device-select::-webkit-scrollbar-thumb {
637
+ background: var(--sw-color-border);
638
+ border-radius: 4px;
639
+ }
640
+
641
+ .device-select::-webkit-scrollbar-thumb:hover {
642
+ background: var(--sw-color-text-muted);
643
+ }
644
+ `;
645
+ o.CSS_VISIBILITY_CHECK_INTERVAL_MS = 200;
646
+ c([
647
+ m({ type: Object })
648
+ ], o.prototype, "deviceController", 2);
649
+ c([
650
+ m({ type: Boolean, attribute: "show-preview" })
651
+ ], o.prototype, "showPreview", 2);
652
+ c([
653
+ n()
654
+ ], o.prototype, "_audioInputDevices", 2);
655
+ c([
656
+ n()
657
+ ], o.prototype, "_videoInputDevices", 2);
658
+ c([
659
+ n()
660
+ ], o.prototype, "_audioOutputDevices", 2);
661
+ c([
662
+ n()
663
+ ], o.prototype, "_selectedAudioInput", 2);
664
+ c([
665
+ n()
666
+ ], o.prototype, "_selectedVideoInput", 2);
667
+ c([
668
+ n()
669
+ ], o.prototype, "_selectedAudioOutput", 2);
670
+ c([
671
+ n()
672
+ ], o.prototype, "_videoPreviewStream", 2);
673
+ c([
674
+ n()
675
+ ], o.prototype, "_audioPreviewStream", 2);
676
+ c([
677
+ n()
678
+ ], o.prototype, "_isTestingAudio", 2);
679
+ o = c([
680
+ g("sw-device-selector")
681
+ ], o);
682
+ export {
683
+ o as DeviceSelector
684
+ };
685
+ //# sourceMappingURL=device-selector.js.map