@signalwire/web-components 1.0.0-dev-20260504184154 → 1.0.0-dev-20260508182243
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/dist/components/UI/controls/sw-ui-dialpad.d.ts +1 -1
- package/dist/components/UI/controls/sw-ui-dialpad.d.ts.map +1 -1
- package/dist/components/UI/controls/sw-ui-dialpad.js +9 -8
- package/dist/components/UI/controls/sw-ui-dialpad.js.map +1 -1
- package/dist/components/UI/controls/sw-ui-dropup.d.ts +1 -1
- package/dist/components/UI/controls/sw-ui-dropup.d.ts.map +1 -1
- package/dist/components/UI/controls/sw-ui-dropup.js +9 -8
- package/dist/components/UI/controls/sw-ui-dropup.js.map +1 -1
- package/dist/components/UI/controls/sw-ui-split-button.d.ts +1 -1
- package/dist/components/UI/controls/sw-ui-split-button.d.ts.map +1 -1
- package/dist/components/UI/controls/sw-ui-split-button.js +13 -12
- package/dist/components/UI/controls/sw-ui-split-button.js.map +1 -1
- package/dist/components/UI/host-reset.d.ts +16 -0
- package/dist/components/UI/host-reset.d.ts.map +1 -0
- package/dist/components/UI/host-reset.js +20 -0
- package/dist/components/UI/host-reset.js.map +1 -0
- package/dist/components/UI/sw-ui-alert.d.ts +1 -1
- package/dist/components/UI/sw-ui-alert.d.ts.map +1 -1
- package/dist/components/UI/sw-ui-alert.js +14 -13
- package/dist/components/UI/sw-ui-alert.js.map +1 -1
- package/dist/components/UI/sw-ui-transcript-view.d.ts +1 -1
- package/dist/components/UI/sw-ui-transcript-view.d.ts.map +1 -1
- package/dist/components/UI/sw-ui-transcript-view.js +6 -5
- package/dist/components/UI/sw-ui-transcript-view.js.map +1 -1
- package/dist/components/sw-call-status.d.ts +1 -1
- package/dist/components/sw-call-status.d.ts.map +1 -1
- package/dist/components/sw-call-status.js +6 -5
- package/dist/components/sw-call-status.js.map +1 -1
- package/dist/components/sw-call-widget/sw-call-widget.d.ts +1 -1
- package/dist/components/sw-call-widget/sw-call-widget.d.ts.map +1 -1
- package/dist/components/sw-call-widget/sw-call-widget.js +14 -13
- package/dist/components/sw-call-widget/sw-call-widget.js.map +1 -1
- package/dist/components/sw-click-to-call.d.ts +1 -1
- package/dist/components/sw-click-to-call.d.ts.map +1 -1
- package/dist/components/sw-click-to-call.js +9 -8
- package/dist/components/sw-click-to-call.js.map +1 -1
- package/dist/components/sw-device-selector/sw-device-selector.d.ts +1 -1
- package/dist/components/sw-device-selector/sw-device-selector.d.ts.map +1 -1
- package/dist/components/sw-device-selector/sw-device-selector.js +5 -4
- package/dist/components/sw-device-selector/sw-device-selector.js.map +1 -1
- package/dist/components/sw-directory.d.ts +1 -1
- package/dist/components/sw-directory.d.ts.map +1 -1
- package/dist/components/sw-directory.js +6 -5
- package/dist/components/sw-directory.js.map +1 -1
- package/dist/components/sw-participant-controls.d.ts +1 -1
- package/dist/components/sw-participant-controls.d.ts.map +1 -1
- package/dist/components/sw-participant-controls.js +6 -5
- package/dist/components/sw-participant-controls.js.map +1 -1
- package/dist/components/sw-participants.d.ts +1 -1
- package/dist/components/sw-participants.d.ts.map +1 -1
- package/dist/components/sw-participants.js +20 -19
- package/dist/components/sw-participants.js.map +1 -1
- package/dist/embed/signalwire-web-components-embed.iife.js +109 -95
- package/dist/embed/signalwire-web-components-embed.iife.js.map +1 -1
- package/dist/embed/signalwire-web-components-embed.umd.cjs +109 -95
- package/dist/embed/signalwire-web-components-embed.umd.cjs.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-device-selector.js","sources":["../../../src/components/sw-device-selector/sw-device-selector.ts"],"sourcesContent":["import { LitElement, html, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { DeviceController } from '@signalwire/js';\nimport { getLogger } from '@signalwire/js';\nimport type { IconName } from '../UI/icons/icons.js';\nimport { deviceSelectorStyles } from './sw-device-selector.styles.js';\nimport '../UI/icons/sw-ui-icon.js';\nimport '../sw-local-camera.js';\nimport '../sw-audio-level.js';\n\nconst logger = getLogger();\n\ntype AudioElementWithSinkId = HTMLAudioElement & {\n setSinkId?: (sinkId: string) => Promise<void>;\n};\n\n/**\n * Single-button device settings popover. Opens a panel above the button\n * with sections for Microphone, Camera, and Speaker selection.\n *\n * When `show-preview` is enabled, the open panel also renders a live\n * camera preview, a microphone level meter for the currently selected\n * input devices, and a \"Test speaker\" button that plays a short tone\n * through the selected output.\n *\n * @fires sw-device-change - User picked a device. Detail: `{ type: 'microphone'|'camera'|'speaker', device: MediaDeviceInfo }`.\n * @cssprop --ctrl-* - all primitive ctrl tokens\n */\n@customElement('sw-device-selector')\nexport class SwDeviceSelector extends LitElement {\n static styles = deviceSelectorStyles;\n\n @property({ type: Object }) deviceController?: DeviceController;\n\n /**\n * Render inline previews (camera video, mic level, speaker test) inside\n * each section while the panel is open. Streams are acquired only while\n * the panel is open and stopped when it closes.\n */\n @property({ type: Boolean, reflect: true, attribute: 'show-preview' })\n showPreview = false;\n\n @state() private _videoStream: MediaStream | null = null;\n @state() private _audioStream: MediaStream | null = null;\n @state() private _testingSpeaker = false;\n\n @state() private _audioIn: MediaDeviceInfo[] = [];\n @state() private _videoIn: MediaDeviceInfo[] = [];\n @state() private _audioOut: MediaDeviceInfo[] = [];\n @state() private _selAudioIn: MediaDeviceInfo | null = null;\n @state() private _selVideoIn: MediaDeviceInfo | null = null;\n @state() private _selAudioOut: MediaDeviceInfo | null = null;\n @state() private _open = false;\n\n private _subs: Subscription[] = [];\n\n private _onDocumentClick = (e: MouseEvent) => {\n if (!e.composedPath().includes(this)) this._open = false;\n };\n\n connectedCallback() {\n super.connectedCallback();\n document.addEventListener('click', this._onDocumentClick);\n this._subscribe();\n }\n\n protected updated(changed: Map<string, unknown>): void {\n super.updated(changed);\n if (changed.has('deviceController')) {\n this._unsubscribe();\n this._subscribe();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('click', this._onDocumentClick);\n this._unsubscribe();\n this._stopVideoPreview();\n this._stopAudioPreview();\n this._stopTestTone();\n }\n\n protected willUpdate(changed: Map<string, unknown>): void {\n if (!this.showPreview) {\n if (changed.has('showPreview') || changed.has('_open')) {\n this._stopVideoPreview();\n this._stopAudioPreview();\n }\n return;\n }\n\n const opened = changed.has('_open') && this._open;\n const closed = changed.has('_open') && !this._open;\n const cameraChanged = changed.has('_selVideoIn');\n const micChanged = changed.has('_selAudioIn');\n\n if (closed) {\n this._stopVideoPreview();\n this._stopAudioPreview();\n this._stopTestTone();\n return;\n }\n\n if (this._open && (opened || cameraChanged) && this._selVideoIn) {\n this._startVideoPreview(this._selVideoIn.deviceId);\n }\n if (this._open && (opened || micChanged) && this._selAudioIn) {\n this._startAudioPreview(this._selAudioIn.deviceId);\n }\n }\n\n // ── subscriptions ──────────────────────────────────────────────────\n\n private _subscribe(): void {\n const dc = this.deviceController;\n if (!dc) return;\n\n this._subs.push(dc.audioInputDevices$.subscribe((d) => { this._audioIn = d; }));\n this._subs.push(dc.videoInputDevices$.subscribe((d) => { this._videoIn = d; }));\n this._subs.push(dc.audioOutputDevices$.subscribe((d) => { this._audioOut = d; }));\n\n if (dc.selectedAudioInputDevice$)\n this._subs.push(dc.selectedAudioInputDevice$.subscribe((d) => { this._selAudioIn = d; }));\n if (dc.selectedVideoInputDevice$)\n this._subs.push(dc.selectedVideoInputDevice$.subscribe((d) => { this._selVideoIn = d; }));\n if (dc.selectedAudioOutputDevice$)\n this._subs.push(dc.selectedAudioOutputDevice$.subscribe((d) => { this._selAudioOut = d; }));\n }\n\n private _unsubscribe(): void {\n this._subs.forEach((s) => s.unsubscribe());\n this._subs = [];\n }\n\n // ── handlers ──────────────────────────────────────────────────────\n\n private _toggle(e: Event) {\n e.stopPropagation();\n this._open = !this._open;\n }\n\n private _selectMic(deviceId: string) {\n const device = this._audioIn.find((d) => d.deviceId === deviceId) ?? null;\n this._selAudioIn = device;\n this.deviceController?.selectAudioInputDevice?.(device);\n if (device) this._emitChange('microphone', device);\n }\n\n private _selectCamera(deviceId: string) {\n const device = this._videoIn.find((d) => d.deviceId === deviceId) ?? null;\n this._selVideoIn = device;\n this.deviceController?.selectVideoInputDevice?.(device);\n if (device) this._emitChange('camera', device);\n }\n\n private _selectSpeaker(deviceId: string) {\n const device = this._audioOut.find((d) => d.deviceId === deviceId) ?? null;\n this._selAudioOut = device;\n this.deviceController?.selectAudioOutputDevice?.(device);\n if (device) this._emitChange('speaker', device);\n }\n\n // ── preview streams ───────────────────────────────────────────────\n\n private async _startVideoPreview(deviceId: string): Promise<void> {\n this._stopVideoPreview();\n try {\n this._videoStream = await navigator.mediaDevices.getUserMedia({\n video: { deviceId: { exact: deviceId } }\n });\n } catch (error) {\n logger.error('sw-device-selector: failed to start camera preview', error);\n this._videoStream = null;\n }\n }\n\n private _stopVideoPreview(): void {\n this._videoStream?.getTracks().forEach((t) => t.stop());\n this._videoStream = null;\n }\n\n private async _startAudioPreview(deviceId: string): Promise<void> {\n this._stopAudioPreview();\n try {\n this._audioStream = await navigator.mediaDevices.getUserMedia({\n audio: { deviceId: { exact: deviceId } }\n });\n } catch (error) {\n logger.error('sw-device-selector: failed to start microphone preview', error);\n this._audioStream = null;\n }\n }\n\n private _stopAudioPreview(): void {\n this._audioStream?.getTracks().forEach((t) => t.stop());\n this._audioStream = null;\n }\n\n // ── speaker test ──────────────────────────────────────────────────\n\n private static readonly _TEST_TONE_DATA_URI =\n 'data:audio/wav;base64,UklGRrQEAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YZAEAACAh46Sl5qcm5mWko2HgXp1cGxqaWlsb3R5gIaNk5icnp2bl5OOiIJ8dnFtamhoam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamlo';\n\n private _testAudio?: AudioElementWithSinkId;\n\n private async _testSpeaker(): Promise<void> {\n if (this._testingSpeaker) {\n this._stopTestTone();\n return;\n }\n const audio = new Audio(SwDeviceSelector._TEST_TONE_DATA_URI) as AudioElementWithSinkId;\n audio.loop = true;\n this._testAudio = audio;\n\n const sinkId = this._selAudioOut?.deviceId;\n if (sinkId && typeof audio.setSinkId === 'function') {\n try {\n await audio.setSinkId(sinkId);\n } catch (error) {\n logger.error('sw-device-selector: setSinkId failed', error);\n }\n }\n audio.addEventListener('ended', () => this._stopTestTone());\n try {\n await audio.play();\n this._testingSpeaker = true;\n } catch (error) {\n logger.error('sw-device-selector: failed to play test tone', error);\n this._stopTestTone();\n }\n }\n\n private _stopTestTone(): void {\n if (this._testAudio) {\n this._testAudio.pause();\n this._testAudio.src = '';\n this._testAudio = undefined;\n }\n this._testingSpeaker = false;\n }\n\n private _emitChange(type: 'microphone' | 'camera' | 'speaker', device: MediaDeviceInfo) {\n this.dispatchEvent(new CustomEvent('sw-device-change', {\n detail: { type, device },\n bubbles: true,\n composed: true\n }));\n }\n\n // ── render helpers ────────────────────────────────────────────────\n\n private _renderSection(\n iconName: IconName,\n title: string,\n devices: MediaDeviceInfo[],\n selectedId: string | undefined,\n onSelect: (id: string) => void,\n preview?: unknown\n ) {\n return html`\n <div class=\"section\">\n <div class=\"section-header\">\n <sw-ui-icon name=${iconName} size=\"13\"></sw-ui-icon>\n <span>${title}</span>\n </div>\n ${devices.length === 0\n ? html`<div class=\"no-devices\">No devices found</div>`\n : devices.map((d, i) => html`\n <div\n class=\"device-item ${d.deviceId === selectedId ? 'selected' : ''}\"\n role=\"option\"\n aria-selected=${d.deviceId === selectedId}\n @click=${() => onSelect(d.deviceId)}\n >\n <svg class=\"check\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M6.5 12L2 7.5l1.4-1.4 3.1 3.1 5.6-5.6 1.4 1.4z\" />\n </svg>\n <span class=\"device-label\">${d.label || `Device ${i + 1}`}</span>\n </div>\n `)\n }\n ${preview ?? nothing}\n </div>\n `;\n }\n\n private _renderCameraPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-camera\" part=\"preview-camera\">\n ${this._videoStream\n ? html`<sw-local-camera .stream=${this._videoStream} mirror></sw-local-camera>`\n : html`<div class=\"preview-empty\">Starting camera…</div>`}\n </div>\n `;\n }\n\n private _renderMicPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-mic\" part=\"preview-mic\">\n ${this._audioStream\n ? html`<sw-audio-level\n .stream=${this._audioStream}\n bars=\"9\"\n orientation=\"horizontal\"\n maxSize=\"22\"\n ></sw-audio-level>`\n : html`<div class=\"preview-empty\">Starting microphone…</div>`}\n </div>\n `;\n }\n\n private _renderSpeakerPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-speaker\" part=\"preview-speaker\">\n <button\n class=\"test-speaker\"\n part=\"test-speaker-button\"\n ?disabled=${!this._selAudioOut}\n @click=${(e: Event) => { e.stopPropagation(); this._testSpeaker(); }}\n >\n <sw-ui-icon name=${this._testingSpeaker ? 'speaker-off' : 'speaker-on'} size=\"14\"></sw-ui-icon>\n <span>${this._testingSpeaker ? 'Stop test' : 'Test speaker'}</span>\n </button>\n </div>\n `;\n }\n\n // ── render ─────────────────────────────────────────────────────────\n\n render() {\n const hasDevices = this._audioIn.length > 0 || this._videoIn.length > 0 || this._audioOut.length > 0;\n\n return html`\n ${this._open ? html`\n <div\n class=\"panel\"\n role=\"listbox\"\n @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape') this._open = false; }}\n >\n ${this._renderSection('mic-on', 'Microphone', this._audioIn, this._selAudioIn?.deviceId, (id) => this._selectMic(id), this._renderMicPreview())}\n ${this._renderSection('camera-on', 'Camera', this._videoIn, this._selVideoIn?.deviceId, (id) => this._selectCamera(id), this._renderCameraPreview())}\n ${this._renderSection('speaker-on', 'Speaker', this._audioOut, this._selAudioOut?.deviceId, (id) => this._selectSpeaker(id), this._renderSpeakerPreview())}\n </div>\n ` : nothing}\n\n <button\n class=\"trigger\"\n aria-label=\"Device settings\"\n aria-expanded=${this._open}\n aria-haspopup=\"listbox\"\n ?disabled=${!hasDevices}\n @click=${this._toggle}\n part=\"button\"\n >\n <sw-ui-icon name=\"settings\" size=\"22\"></sw-ui-icon>\n </button>\n <span class=\"label\">Devices</span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-device-selector': SwDeviceSelector;\n }\n}\n"],"names":["logger","getLogger","SwDeviceSelector","LitElement","changed","opened","closed","cameraChanged","micChanged","dc","d","s","deviceId","device","_b","_a","error","t","audio","sinkId","type","iconName","title","devices","selectedId","onSelect","preview","html","i","nothing","hasDevices","e","id","_c","deviceSelectorStyles","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sw-device-selector.js","sources":["../../../src/components/sw-device-selector/sw-device-selector.ts"],"sourcesContent":["import { LitElement, html, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { DeviceController } from '@signalwire/js';\nimport { getLogger } from '@signalwire/js';\nimport type { IconName } from '../UI/icons/icons.js';\nimport { deviceSelectorStyles } from './sw-device-selector.styles.js';\nimport { hostReset } from '../UI/host-reset.js';\nimport '../UI/icons/sw-ui-icon.js';\nimport '../sw-local-camera.js';\nimport '../sw-audio-level.js';\n\nconst logger = getLogger();\n\ntype AudioElementWithSinkId = HTMLAudioElement & {\n setSinkId?: (sinkId: string) => Promise<void>;\n};\n\n/**\n * Single-button device settings popover. Opens a panel above the button\n * with sections for Microphone, Camera, and Speaker selection.\n *\n * When `show-preview` is enabled, the open panel also renders a live\n * camera preview, a microphone level meter for the currently selected\n * input devices, and a \"Test speaker\" button that plays a short tone\n * through the selected output.\n *\n * @fires sw-device-change - User picked a device. Detail: `{ type: 'microphone'|'camera'|'speaker', device: MediaDeviceInfo }`.\n * @cssprop --ctrl-* - all primitive ctrl tokens\n */\n@customElement('sw-device-selector')\nexport class SwDeviceSelector extends LitElement {\n static styles = [hostReset, deviceSelectorStyles];\n\n @property({ type: Object }) deviceController?: DeviceController;\n\n /**\n * Render inline previews (camera video, mic level, speaker test) inside\n * each section while the panel is open. Streams are acquired only while\n * the panel is open and stopped when it closes.\n */\n @property({ type: Boolean, reflect: true, attribute: 'show-preview' })\n showPreview = false;\n\n @state() private _videoStream: MediaStream | null = null;\n @state() private _audioStream: MediaStream | null = null;\n @state() private _testingSpeaker = false;\n\n @state() private _audioIn: MediaDeviceInfo[] = [];\n @state() private _videoIn: MediaDeviceInfo[] = [];\n @state() private _audioOut: MediaDeviceInfo[] = [];\n @state() private _selAudioIn: MediaDeviceInfo | null = null;\n @state() private _selVideoIn: MediaDeviceInfo | null = null;\n @state() private _selAudioOut: MediaDeviceInfo | null = null;\n @state() private _open = false;\n\n private _subs: Subscription[] = [];\n\n private _onDocumentClick = (e: MouseEvent) => {\n if (!e.composedPath().includes(this)) this._open = false;\n };\n\n connectedCallback() {\n super.connectedCallback();\n document.addEventListener('click', this._onDocumentClick);\n this._subscribe();\n }\n\n protected updated(changed: Map<string, unknown>): void {\n super.updated(changed);\n if (changed.has('deviceController')) {\n this._unsubscribe();\n this._subscribe();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('click', this._onDocumentClick);\n this._unsubscribe();\n this._stopVideoPreview();\n this._stopAudioPreview();\n this._stopTestTone();\n }\n\n protected willUpdate(changed: Map<string, unknown>): void {\n if (!this.showPreview) {\n if (changed.has('showPreview') || changed.has('_open')) {\n this._stopVideoPreview();\n this._stopAudioPreview();\n }\n return;\n }\n\n const opened = changed.has('_open') && this._open;\n const closed = changed.has('_open') && !this._open;\n const cameraChanged = changed.has('_selVideoIn');\n const micChanged = changed.has('_selAudioIn');\n\n if (closed) {\n this._stopVideoPreview();\n this._stopAudioPreview();\n this._stopTestTone();\n return;\n }\n\n if (this._open && (opened || cameraChanged) && this._selVideoIn) {\n this._startVideoPreview(this._selVideoIn.deviceId);\n }\n if (this._open && (opened || micChanged) && this._selAudioIn) {\n this._startAudioPreview(this._selAudioIn.deviceId);\n }\n }\n\n // ── subscriptions ──────────────────────────────────────────────────\n\n private _subscribe(): void {\n const dc = this.deviceController;\n if (!dc) return;\n\n this._subs.push(dc.audioInputDevices$.subscribe((d) => { this._audioIn = d; }));\n this._subs.push(dc.videoInputDevices$.subscribe((d) => { this._videoIn = d; }));\n this._subs.push(dc.audioOutputDevices$.subscribe((d) => { this._audioOut = d; }));\n\n if (dc.selectedAudioInputDevice$)\n this._subs.push(dc.selectedAudioInputDevice$.subscribe((d) => { this._selAudioIn = d; }));\n if (dc.selectedVideoInputDevice$)\n this._subs.push(dc.selectedVideoInputDevice$.subscribe((d) => { this._selVideoIn = d; }));\n if (dc.selectedAudioOutputDevice$)\n this._subs.push(dc.selectedAudioOutputDevice$.subscribe((d) => { this._selAudioOut = d; }));\n }\n\n private _unsubscribe(): void {\n this._subs.forEach((s) => s.unsubscribe());\n this._subs = [];\n }\n\n // ── handlers ──────────────────────────────────────────────────────\n\n private _toggle(e: Event) {\n e.stopPropagation();\n this._open = !this._open;\n }\n\n private _selectMic(deviceId: string) {\n const device = this._audioIn.find((d) => d.deviceId === deviceId) ?? null;\n this._selAudioIn = device;\n this.deviceController?.selectAudioInputDevice?.(device);\n if (device) this._emitChange('microphone', device);\n }\n\n private _selectCamera(deviceId: string) {\n const device = this._videoIn.find((d) => d.deviceId === deviceId) ?? null;\n this._selVideoIn = device;\n this.deviceController?.selectVideoInputDevice?.(device);\n if (device) this._emitChange('camera', device);\n }\n\n private _selectSpeaker(deviceId: string) {\n const device = this._audioOut.find((d) => d.deviceId === deviceId) ?? null;\n this._selAudioOut = device;\n this.deviceController?.selectAudioOutputDevice?.(device);\n if (device) this._emitChange('speaker', device);\n }\n\n // ── preview streams ───────────────────────────────────────────────\n\n private async _startVideoPreview(deviceId: string): Promise<void> {\n this._stopVideoPreview();\n try {\n this._videoStream = await navigator.mediaDevices.getUserMedia({\n video: { deviceId: { exact: deviceId } }\n });\n } catch (error) {\n logger.error('sw-device-selector: failed to start camera preview', error);\n this._videoStream = null;\n }\n }\n\n private _stopVideoPreview(): void {\n this._videoStream?.getTracks().forEach((t) => t.stop());\n this._videoStream = null;\n }\n\n private async _startAudioPreview(deviceId: string): Promise<void> {\n this._stopAudioPreview();\n try {\n this._audioStream = await navigator.mediaDevices.getUserMedia({\n audio: { deviceId: { exact: deviceId } }\n });\n } catch (error) {\n logger.error('sw-device-selector: failed to start microphone preview', error);\n this._audioStream = null;\n }\n }\n\n private _stopAudioPreview(): void {\n this._audioStream?.getTracks().forEach((t) => t.stop());\n this._audioStream = null;\n }\n\n // ── speaker test ──────────────────────────────────────────────────\n\n private static readonly _TEST_TONE_DATA_URI =\n 'data:audio/wav;base64,UklGRrQEAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YZAEAACAh46Sl5qcm5mWko2HgXp1cGxqaWlsb3R5gIaNk5icnp2bl5OOiIJ8dnFtamhoam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamloam1xdnyChI+VmZ2enZqXkoyGgHt1cG1qaGhrbnJ4foSLkZianp6cmZWQioR+eHJuamlo';\n\n private _testAudio?: AudioElementWithSinkId;\n\n private async _testSpeaker(): Promise<void> {\n if (this._testingSpeaker) {\n this._stopTestTone();\n return;\n }\n const audio = new Audio(SwDeviceSelector._TEST_TONE_DATA_URI) as AudioElementWithSinkId;\n audio.loop = true;\n this._testAudio = audio;\n\n const sinkId = this._selAudioOut?.deviceId;\n if (sinkId && typeof audio.setSinkId === 'function') {\n try {\n await audio.setSinkId(sinkId);\n } catch (error) {\n logger.error('sw-device-selector: setSinkId failed', error);\n }\n }\n audio.addEventListener('ended', () => this._stopTestTone());\n try {\n await audio.play();\n this._testingSpeaker = true;\n } catch (error) {\n logger.error('sw-device-selector: failed to play test tone', error);\n this._stopTestTone();\n }\n }\n\n private _stopTestTone(): void {\n if (this._testAudio) {\n this._testAudio.pause();\n this._testAudio.src = '';\n this._testAudio = undefined;\n }\n this._testingSpeaker = false;\n }\n\n private _emitChange(type: 'microphone' | 'camera' | 'speaker', device: MediaDeviceInfo) {\n this.dispatchEvent(new CustomEvent('sw-device-change', {\n detail: { type, device },\n bubbles: true,\n composed: true\n }));\n }\n\n // ── render helpers ────────────────────────────────────────────────\n\n private _renderSection(\n iconName: IconName,\n title: string,\n devices: MediaDeviceInfo[],\n selectedId: string | undefined,\n onSelect: (id: string) => void,\n preview?: unknown\n ) {\n return html`\n <div class=\"section\">\n <div class=\"section-header\">\n <sw-ui-icon name=${iconName} size=\"13\"></sw-ui-icon>\n <span>${title}</span>\n </div>\n ${devices.length === 0\n ? html`<div class=\"no-devices\">No devices found</div>`\n : devices.map((d, i) => html`\n <div\n class=\"device-item ${d.deviceId === selectedId ? 'selected' : ''}\"\n role=\"option\"\n aria-selected=${d.deviceId === selectedId}\n @click=${() => onSelect(d.deviceId)}\n >\n <svg class=\"check\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M6.5 12L2 7.5l1.4-1.4 3.1 3.1 5.6-5.6 1.4 1.4z\" />\n </svg>\n <span class=\"device-label\">${d.label || `Device ${i + 1}`}</span>\n </div>\n `)\n }\n ${preview ?? nothing}\n </div>\n `;\n }\n\n private _renderCameraPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-camera\" part=\"preview-camera\">\n ${this._videoStream\n ? html`<sw-local-camera .stream=${this._videoStream} mirror></sw-local-camera>`\n : html`<div class=\"preview-empty\">Starting camera…</div>`}\n </div>\n `;\n }\n\n private _renderMicPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-mic\" part=\"preview-mic\">\n ${this._audioStream\n ? html`<sw-audio-level\n .stream=${this._audioStream}\n bars=\"9\"\n orientation=\"horizontal\"\n maxSize=\"22\"\n ></sw-audio-level>`\n : html`<div class=\"preview-empty\">Starting microphone…</div>`}\n </div>\n `;\n }\n\n private _renderSpeakerPreview() {\n if (!this.showPreview) return null;\n return html`\n <div class=\"preview preview-speaker\" part=\"preview-speaker\">\n <button\n class=\"test-speaker\"\n part=\"test-speaker-button\"\n ?disabled=${!this._selAudioOut}\n @click=${(e: Event) => { e.stopPropagation(); this._testSpeaker(); }}\n >\n <sw-ui-icon name=${this._testingSpeaker ? 'speaker-off' : 'speaker-on'} size=\"14\"></sw-ui-icon>\n <span>${this._testingSpeaker ? 'Stop test' : 'Test speaker'}</span>\n </button>\n </div>\n `;\n }\n\n // ── render ─────────────────────────────────────────────────────────\n\n render() {\n const hasDevices = this._audioIn.length > 0 || this._videoIn.length > 0 || this._audioOut.length > 0;\n\n return html`\n ${this._open ? html`\n <div\n class=\"panel\"\n role=\"listbox\"\n @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape') this._open = false; }}\n >\n ${this._renderSection('mic-on', 'Microphone', this._audioIn, this._selAudioIn?.deviceId, (id) => this._selectMic(id), this._renderMicPreview())}\n ${this._renderSection('camera-on', 'Camera', this._videoIn, this._selVideoIn?.deviceId, (id) => this._selectCamera(id), this._renderCameraPreview())}\n ${this._renderSection('speaker-on', 'Speaker', this._audioOut, this._selAudioOut?.deviceId, (id) => this._selectSpeaker(id), this._renderSpeakerPreview())}\n </div>\n ` : nothing}\n\n <button\n class=\"trigger\"\n aria-label=\"Device settings\"\n aria-expanded=${this._open}\n aria-haspopup=\"listbox\"\n ?disabled=${!hasDevices}\n @click=${this._toggle}\n part=\"button\"\n >\n <sw-ui-icon name=\"settings\" size=\"22\"></sw-ui-icon>\n </button>\n <span class=\"label\">Devices</span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-device-selector': SwDeviceSelector;\n }\n}\n"],"names":["logger","getLogger","SwDeviceSelector","LitElement","changed","opened","closed","cameraChanged","micChanged","dc","d","s","deviceId","device","_b","_a","error","t","audio","sinkId","type","iconName","title","devices","selectedId","onSelect","preview","html","i","nothing","hasDevices","e","id","_c","hostReset","deviceSelectorStyles","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
@@ -49,7 +49,7 @@ export interface DirectoryService {
|
|
|
49
49
|
loadMore?(): Promise<void>;
|
|
50
50
|
}
|
|
51
51
|
export declare class SwDirectory extends LitElement {
|
|
52
|
-
static styles: import("lit").CSSResult;
|
|
52
|
+
static styles: import("lit").CSSResult[];
|
|
53
53
|
/**
|
|
54
54
|
* Directory service with addresses$ observable
|
|
55
55
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-directory.d.ts","sourceRoot":"","sources":["../../src/components/sw-directory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"sw-directory.d.ts","sourceRoot":"","sources":["../../src/components/sw-directory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,0BAA0B,CAAC;AAGlC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,qBACa,WAAY,SAAQ,UAAU;IACzC,MAAM,CAAC,MAAM,4BA+MV;IAEH;;OAEG;IAEH,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IAE1C;;OAEG;IAEH,OAAO,CAAC,eAAe,CAAwB;IAE/C;;OAEG;IAEH,OAAO,CAAC,WAAW,CAAc;IAEjC;;OAEG;IAEH,OAAO,CAAC,SAAS,CAAiB;IAElC;;OAEG;IAEH,OAAO,CAAC,OAAO,CAAkB;IAEjC;;OAEG;IAEH,OAAO,CAAC,OAAO,CAAkB;IAEjC;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAuB;IAElD;;OAEG;IACH,OAAO,CAAC,oBAAoB,CAAqC;IAEjE;;OAEG;IAEH,OAAO,CAAC,sBAAsB,CAAkB;IAEhD,iBAAiB;IAKjB,oBAAoB;IAKpB,OAAO,CAAC,oBAAoB;IAiC5B,OAAO,CAAC,OAAO;IAYf,SAAS,CAAC,YAAY;IAItB,OAAO,CAAC,mBAAmB;IAqB3B,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;YAyB3C,sBAAsB;IAiBpC,OAAO,KAAK,iBAAiB,GAU5B;IAED,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,UAAU;YAUJ,cAAc;IAM5B,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,sBAAsB;IAS9B,MAAM;CAuEP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { LitElement as u, html as a, css as p } from "lit";
|
|
2
2
|
import { property as m, state as l, customElement as b } from "lit/decorators.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { hostReset as v } from "./UI/host-reset.js";
|
|
4
|
+
var f = Object.defineProperty, g = Object.getOwnPropertyDescriptor, n = (e, i, o, t) => {
|
|
5
|
+
for (var s = t > 1 ? void 0 : t ? g(i, o) : i, c = e.length - 1, d; c >= 0; c--)
|
|
5
6
|
(d = e[c]) && (s = (t ? d(i, o, s) : d(s)) || s);
|
|
6
|
-
return t && s &&
|
|
7
|
+
return t && s && f(i, o, s), s;
|
|
7
8
|
};
|
|
8
9
|
let r = class extends u {
|
|
9
10
|
constructor() {
|
|
@@ -196,7 +197,7 @@ let r = class extends u {
|
|
|
196
197
|
`;
|
|
197
198
|
}
|
|
198
199
|
};
|
|
199
|
-
r.styles = p`
|
|
200
|
+
r.styles = [v, p`
|
|
200
201
|
:host {
|
|
201
202
|
display: block;
|
|
202
203
|
font-family: var(--type-family-body);
|
|
@@ -403,7 +404,7 @@ r.styles = p`
|
|
|
403
404
|
.dial-button sw-ui-icon {
|
|
404
405
|
pointer-events: none;
|
|
405
406
|
}
|
|
406
|
-
|
|
407
|
+
`];
|
|
407
408
|
n([
|
|
408
409
|
m({ type: Object })
|
|
409
410
|
], r.prototype, "directory", 2);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-directory.js","sources":["../../src/components/sw-directory.ts"],"sourcesContent":["/**\n * Directory Component\n *\n * A searchable list of addresses from the directory service.\n * Supports filtering, selection, and pagination.\n *\n * @example\n * ```html\n * <sw-directory .directory=${directory}></sw-directory>\n * ```\n *\n * @fires sw-address-select - Fired when an address is selected. Detail: `{ address: Address }`\n * @fires sw-dial - Fired when the call button on an address is clicked. Detail: `{ address: Address, channel: string }`\n *\n * @cssprop [--interactive-button-primary-bg=#044ef4] - Primary brand color\n * @cssprop [--interactive-button-primary-hover=#0342cf] - Primary color on hover\n * @cssprop [--interactive-status-success=#22c55e] - Success/positive color\n * @cssprop [--fg-default=#f0f0f4] - Primary text color\n * @cssprop [--fg-muted=#a0a0aa] - Secondary/muted text color\n * @cssprop [--bg-surface=#181a28] - Component background color\n * @cssprop [--bg-surface-raised=#222436] - Background color on hover\n * @cssprop [--interactive-dropdown-hover=#333338] - Background color on active/press\n * @cssprop [--border-default=rgba(255,255,255,0.12)] - Border color\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { Observable } from 'rxjs';\nimport './UI/icons/sw-ui-icon.js';\n\n/**\n * Address type from SDK\n */\nexport interface Address {\n id: string;\n name: string;\n displayName?: string;\n type?: 'room' | 'person';\n channels?: {\n audio?: boolean;\n video?: boolean;\n messaging?: boolean;\n };\n}\n\n/**\n * Directory interface for component\n */\nexport interface DirectoryService {\n addresses$: Observable<Address[]>;\n loading$?: Observable<boolean>;\n hasMore$?: Observable<boolean>;\n loadMore?(): Promise<void>;\n}\n\n@customElement('sw-directory')\nexport class SwDirectory extends LitElement {\n static styles = css`\n :host {\n display: block;\n font-family: var(--type-family-body);\n color: var(--fg-default);\n }\n\n .container {\n display: flex;\n flex-direction: column;\n background: var(--bg-surface);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n overflow: hidden;\n }\n\n .search {\n display: flex;\n align-items: center;\n gap: var(--sp-2);\n padding: var(--sp-3);\n border-bottom: 1px solid var(--border-default);\n }\n\n .search-input {\n flex: 1;\n padding: var(--sp-2) var(--sp-3);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n background: var(--bg-surface);\n color: var(--fg-default);\n font-family: var(--type-family-body);\n font-size: var(--type-size-small);\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .search-input:focus {\n border-color: var(--interactive-button-primary-bg);\n }\n\n .search-input::placeholder {\n color: var(--fg-muted);\n }\n\n .list {\n display: flex;\n flex-direction: column;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .item {\n display: flex;\n align-items: center;\n gap: var(--sp-3);\n padding: var(--sp-3) var(--sp-4);\n border-bottom: 1px solid var(--border-default);\n cursor: pointer;\n transition: background-color 0.15s ease;\n }\n\n .item:last-child {\n border-bottom: none;\n }\n\n .item:hover {\n background: var(--bg-surface-raised);\n }\n\n .item:active {\n background: var(--interactive-dropdown-hover);\n }\n\n .item.selected {\n background: var(--interactive-button-primary-bg);\n color: white;\n }\n\n .item.selected .item-type,\n .item.selected .item-channels {\n color: rgba(255, 255, 255, 0.8);\n }\n\n .item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: var(--bg-surface-raised);\n color: var(--fg-muted);\n flex-shrink: 0;\n }\n\n .item.selected .item-icon {\n background: rgba(255, 255, 255, 0.2);\n color: white;\n }\n\n .item-icon svg,\n .item-icon sw-ui-icon {\n width: 20px;\n height: 20px;\n }\n\n .item-content {\n flex: 1;\n min-width: 0;\n }\n\n .item-name {\n font-size: var(--type-size-small);\n font-weight: 500;\n color: inherit;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .item-type {\n font-size: var(--type-size-caption);\n color: var(--fg-muted);\n text-transform: capitalize;\n }\n\n .item-channels {\n display: flex;\n gap: var(--sp-1);\n color: var(--fg-muted);\n }\n\n .item-channels sw-ui-icon {\n width: 16px;\n height: 16px;\n }\n\n .item-channels sw-ui-icon.active {\n color: var(--interactive-status-success);\n }\n\n .loading,\n .empty {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--sp-4);\n color: var(--fg-muted);\n font-size: var(--type-size-small);\n }\n\n .scroll-sentinel {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--sp-3);\n min-height: 1px;\n }\n\n .scroll-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sp-2);\n padding: var(--sp-3);\n color: var(--fg-muted);\n font-size: var(--type-size-caption);\n }\n\n .spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--border-default);\n border-top-color: var(--interactive-button-primary-bg);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n .dial-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: var(--interactive-status-success);\n border: none;\n border-radius: 50%;\n color: white;\n cursor: pointer;\n flex-shrink: 0;\n transition: background-color 0.15s ease;\n }\n\n .dial-button:hover {\n background: #0ea472;\n }\n\n .dial-button sw-ui-icon {\n pointer-events: none;\n }\n `;\n\n /**\n * Directory service with addresses$ observable\n */\n @property({ type: Object })\n directory: DirectoryService | null = null;\n\n /**\n * Currently selected address\n */\n @state()\n private selectedAddress: Address | null = null;\n\n /**\n * Search filter query\n */\n @state()\n private searchQuery: string = '';\n\n /**\n * List of addresses from directory\n */\n @state()\n private addresses: Address[] = [];\n\n /**\n * Loading state\n */\n @state()\n private loading: boolean = false;\n\n /**\n * Has more addresses to load\n */\n @state()\n private hasMore: boolean = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Debounce timer for search\n */\n private searchDebounceTimer: number | null = null;\n\n /**\n * IntersectionObserver for infinite scroll\n */\n private intersectionObserver: IntersectionObserver | null = null;\n\n /**\n * Flag to track if we're auto-loading for search\n */\n @state()\n private isAutoLoadingForSearch: boolean = false;\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToDirectory();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n private subscribeToDirectory() {\n if (!this.directory) return;\n\n // Subscribe to addresses\n if (this.directory.addresses$) {\n const addressesSub = this.directory.addresses$.subscribe((addresses) => {\n this.addresses = addresses;\n });\n this.subscriptions.push(addressesSub);\n }\n\n // Subscribe to loading state\n if (this.directory.loading$) {\n const loadingSub = this.directory.loading$.subscribe((loading) => {\n this.loading = loading;\n });\n this.subscriptions.push(loadingSub);\n }\n\n // Subscribe to hasMore\n if (this.directory.hasMore$) {\n const hasMoreSub = this.directory.hasMore$.subscribe((hasMore) => {\n this.hasMore = hasMore;\n });\n this.subscriptions.push(hasMoreSub);\n }\n\n // Load initial data\n if (this.directory.loadMore) {\n this.directory.loadMore();\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n if (this.searchDebounceTimer) {\n clearTimeout(this.searchDebounceTimer);\n }\n if (this.intersectionObserver) {\n this.intersectionObserver.disconnect();\n this.intersectionObserver = null;\n }\n }\n\n protected firstUpdated() {\n this.setupInfiniteScroll();\n }\n\n private setupInfiniteScroll() {\n const sentinel = this.shadowRoot?.querySelector('.scroll-sentinel');\n if (!sentinel) return;\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (entry?.isIntersecting && this.hasMore && !this.loading) {\n this.handleLoadMore();\n }\n },\n {\n root: this.shadowRoot?.querySelector('.list'),\n rootMargin: '100px',\n threshold: 0\n }\n );\n\n this.intersectionObserver.observe(sentinel);\n }\n\n protected updated(changedProperties: Map<string, unknown>) {\n super.updated(changedProperties);\n\n // Handle directory property changes\n if (changedProperties.has('directory')) {\n this.cleanup();\n this.subscribeToDirectory();\n }\n\n // Re-setup infinite scroll if needed\n if (!this.intersectionObserver) {\n this.setupInfiniteScroll();\n }\n\n // Auto-load more when searching and no results found but more available\n // Also check when loading state changes so we can trigger subsequent loads\n if (\n changedProperties.has('addresses') ||\n changedProperties.has('searchQuery') ||\n changedProperties.has('loading')\n ) {\n this.checkAutoLoadForSearch();\n }\n }\n\n private async checkAutoLoadForSearch() {\n // If we have a search query, no filtered results, but more to load, keep loading\n // Note: !this.loading prevents concurrent loads, isAutoLoadingForSearch is for UI state only\n if (\n this.searchQuery.trim() &&\n this.filteredAddresses.length === 0 &&\n this.hasMore &&\n !this.loading\n ) {\n this.isAutoLoadingForSearch = true;\n await this.handleLoadMore();\n // The updated() will be called again when addresses/loading change, continuing the loop\n } else if (this.filteredAddresses.length > 0 || !this.hasMore) {\n this.isAutoLoadingForSearch = false;\n }\n }\n\n private get filteredAddresses(): Address[] {\n if (!this.searchQuery.trim()) {\n return this.addresses;\n }\n const query = this.searchQuery.toLowerCase();\n return this.addresses.filter(\n (addr) =>\n addr.name.toLowerCase().includes(query) ||\n (addr.displayName && addr.displayName.toLowerCase().includes(query))\n );\n }\n\n private handleSearchInput(e: Event) {\n const input = e.target as HTMLInputElement;\n\n // Debounce search\n if (this.searchDebounceTimer) {\n clearTimeout(this.searchDebounceTimer);\n }\n\n this.searchDebounceTimer = window.setTimeout(() => {\n this.searchQuery = input.value;\n }, 200);\n }\n\n private handleItemClick(address: Address) {\n this.selectedAddress = address;\n\n this.dispatchEvent(\n new CustomEvent('sw-address-select', {\n detail: { address },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private handleItemDoubleClick(address: Address) {\n this.handleDial(address);\n }\n\n private handleDial(address: Address) {\n this.dispatchEvent(\n new CustomEvent('sw-dial', {\n detail: { address },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private async handleLoadMore() {\n if (this.directory?.loadMore) {\n await this.directory.loadMore();\n }\n }\n\n private renderAddressIcon(address: Address) {\n const name = address.type === 'room' ? 'room' : 'person';\n return html`<sw-ui-icon name=${name} size=\"20\"></sw-ui-icon>`;\n }\n\n private renderChannelIcons(address: Address) {\n const channels = address.channels || {};\n return html`\n <div class=\"item-channels\">\n <sw-ui-icon\n name=\"mic-on\"\n size=\"16\"\n class=\"${channels.audio ? 'active' : ''}\"\n title=\"Audio\"\n ></sw-ui-icon>\n <sw-ui-icon\n name=\"camera-on\"\n size=\"16\"\n class=\"${channels.video ? 'active' : ''}\"\n title=\"Video\"\n ></sw-ui-icon>\n </div>\n `;\n }\n\n private renderLoadingIndicator() {\n return html`\n <div class=\"scroll-loading\">\n <div class=\"spinner\"></div>\n <span>${this.isAutoLoadingForSearch ? 'Searching...' : 'Loading more...'}</span>\n </div>\n `;\n }\n\n render() {\n const showInitialLoading = this.loading && this.addresses.length === 0;\n const showEmptyState = !this.loading && this.filteredAddresses.length === 0 && !this.hasMore;\n const showSearchingState = this.isAutoLoadingForSearch && this.filteredAddresses.length === 0;\n\n return html`\n <div class=\"container\" part=\"container\">\n <div class=\"search\" part=\"search\">\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search addresses...\"\n @input=${this.handleSearchInput}\n aria-label=\"Search addresses\"\n />\n </div>\n\n <div class=\"list\" part=\"list\" role=\"listbox\">\n ${showInitialLoading\n ? html`<div class=\"loading\">Loading...</div>`\n : showSearchingState\n ? this.renderLoadingIndicator()\n : showEmptyState\n ? html`<div class=\"empty\">No addresses found</div>`\n : html`\n ${this.filteredAddresses.map(\n (address) => html`\n <div\n class=\"item ${this.selectedAddress?.id === address.id ? 'selected' : ''}\"\n part=\"item ${this.selectedAddress?.id === address.id\n ? 'item-selected'\n : ''}\"\n role=\"option\"\n aria-selected=\"${this.selectedAddress?.id === address.id}\"\n @click=${() => this.handleItemClick(address)}\n @dblclick=${() => this.handleItemDoubleClick(address)}\n >\n <div class=\"item-icon\">${this.renderAddressIcon(address)}</div>\n <div class=\"item-content\">\n <div class=\"item-name\" part=\"item-name\">\n ${address.displayName || address.name}\n </div>\n <div class=\"item-type\" part=\"item-type\">\n ${address.type || 'address'}\n </div>\n </div>\n ${this.renderChannelIcons(address)}\n <button\n class=\"dial-button\"\n @click=${(e: Event) => {\n e.stopPropagation();\n this.handleDial(address);\n }}\n aria-label=\"Call ${address.displayName || address.name}\"\n >\n <sw-ui-icon name=\"phone-call\" size=\"18\"></sw-ui-icon>\n </button>\n </div>\n `\n )}\n <!-- Scroll sentinel for infinite scroll -->\n <div class=\"scroll-sentinel\">\n ${this.loading && !this.isAutoLoadingForSearch\n ? this.renderLoadingIndicator()\n : null}\n </div>\n `}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-directory': SwDirectory;\n }\n}\n"],"names":["SwDirectory","LitElement","addressesSub","addresses","loadingSub","loading","hasMoreSub","hasMore","sub","sentinel","_a","entries","entry","_b","changedProperties","query","addr","input","address","name","html","channels","showInitialLoading","showEmptyState","showSearchingState","_c","e","css","__decorateClass","property","state","customElement"],"mappings":";;;;;;;AAyDO,IAAMA,IAAN,cAA0BC,EAAW;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA,GAsNL,KAAA,YAAqC,MAMrC,KAAQ,kBAAkC,MAM1C,KAAQ,cAAsB,IAM9B,KAAQ,YAAuB,CAAA,GAM/B,KAAQ,UAAmB,IAM3B,KAAQ,UAAmB,IAK3B,KAAQ,gBAAgC,CAAA,GAKxC,KAAQ,sBAAqC,MAK7C,KAAQ,uBAAoD,MAM5D,KAAQ,yBAAkC;AAAA,EAAA;AAAA,EAE1C,oBAAoB;AAClB,UAAM,kBAAA,GACN,KAAK,qBAAA;AAAA,EACP;AAAA,EAEA,uBAAuB;AACrB,UAAM,qBAAA,GACN,KAAK,QAAA;AAAA,EACP;AAAA,EAEQ,uBAAuB;AAC7B,QAAK,KAAK,WAGV;AAAA,UAAI,KAAK,UAAU,YAAY;AAC7B,cAAMC,IAAe,KAAK,UAAU,WAAW,UAAU,CAACC,MAAc;AACtE,eAAK,YAAYA;AAAA,QACnB,CAAC;AACD,aAAK,cAAc,KAAKD,CAAY;AAAA,MACtC;AAGA,UAAI,KAAK,UAAU,UAAU;AAC3B,cAAME,IAAa,KAAK,UAAU,SAAS,UAAU,CAACC,MAAY;AAChE,eAAK,UAAUA;AAAA,QACjB,CAAC;AACD,aAAK,cAAc,KAAKD,CAAU;AAAA,MACpC;AAGA,UAAI,KAAK,UAAU,UAAU;AAC3B,cAAME,IAAa,KAAK,UAAU,SAAS,UAAU,CAACC,MAAY;AAChE,eAAK,UAAUA;AAAA,QACjB,CAAC;AACD,aAAK,cAAc,KAAKD,CAAU;AAAA,MACpC;AAGA,MAAI,KAAK,UAAU,YACjB,KAAK,UAAU,SAAA;AAAA;AAAA,EAEnB;AAAA,EAEQ,UAAU;AAChB,SAAK,cAAc,QAAQ,CAACE,MAAQA,EAAI,aAAa,GACrD,KAAK,gBAAgB,CAAA,GACjB,KAAK,uBACP,aAAa,KAAK,mBAAmB,GAEnC,KAAK,yBACP,KAAK,qBAAqB,WAAA,GAC1B,KAAK,uBAAuB;AAAA,EAEhC;AAAA,EAEU,eAAe;AACvB,SAAK,oBAAA;AAAA,EACP;AAAA,EAEQ,sBAAsB;;AAC5B,UAAMC,KAAWC,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAAc;AAChD,IAAKD,MAEL,KAAK,uBAAuB,IAAI;AAAA,MAC9B,CAACE,MAAY;AACX,cAAMC,IAAQD,EAAQ,CAAC;AACvB,QAAIC,KAAA,QAAAA,EAAO,kBAAkB,KAAK,WAAW,CAAC,KAAK,WACjD,KAAK,eAAA;AAAA,MAET;AAAA,MACA;AAAA,QACE,OAAMC,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAAc;AAAA,QACrC,YAAY;AAAA,QACZ,WAAW;AAAA,MAAA;AAAA,IACb,GAGF,KAAK,qBAAqB,QAAQJ,CAAQ;AAAA,EAC5C;AAAA,EAEU,QAAQK,GAAyC;AACzD,UAAM,QAAQA,CAAiB,GAG3BA,EAAkB,IAAI,WAAW,MACnC,KAAK,QAAA,GACL,KAAK,qBAAA,IAIF,KAAK,wBACR,KAAK,oBAAA,IAMLA,EAAkB,IAAI,WAAW,KACjCA,EAAkB,IAAI,aAAa,KACnCA,EAAkB,IAAI,SAAS,MAE/B,KAAK,uBAAA;AAAA,EAET;AAAA,EAEA,MAAc,yBAAyB;AAGrC,IACE,KAAK,YAAY,KAAA,KACjB,KAAK,kBAAkB,WAAW,KAClC,KAAK,WACL,CAAC,KAAK,WAEN,KAAK,yBAAyB,IAC9B,MAAM,KAAK,eAAA,MAEF,KAAK,kBAAkB,SAAS,KAAK,CAAC,KAAK,aACpD,KAAK,yBAAyB;AAAA,EAElC;AAAA,EAEA,IAAY,oBAA+B;AACzC,QAAI,CAAC,KAAK,YAAY;AACpB,aAAO,KAAK;AAEd,UAAMC,IAAQ,KAAK,YAAY,YAAA;AAC/B,WAAO,KAAK,UAAU;AAAA,MACpB,CAACC,MACCA,EAAK,KAAK,YAAA,EAAc,SAASD,CAAK,KACrCC,EAAK,eAAeA,EAAK,YAAY,YAAA,EAAc,SAASD,CAAK;AAAA,IAAA;AAAA,EAExE;AAAA,EAEQ,kBAAkB,GAAU;AAClC,UAAME,IAAQ,EAAE;AAGhB,IAAI,KAAK,uBACP,aAAa,KAAK,mBAAmB,GAGvC,KAAK,sBAAsB,OAAO,WAAW,MAAM;AACjD,WAAK,cAAcA,EAAM;AAAA,IAC3B,GAAG,GAAG;AAAA,EACR;AAAA,EAEQ,gBAAgBC,GAAkB;AACxC,SAAK,kBAAkBA,GAEvB,KAAK;AAAA,MACH,IAAI,YAAY,qBAAqB;AAAA,QACnC,QAAQ,EAAE,SAAAA,EAAA;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,MAAA,CACX;AAAA,IAAA;AAAA,EAEL;AAAA,EAEQ,sBAAsBA,GAAkB;AAC9C,SAAK,WAAWA,CAAO;AAAA,EACzB;AAAA,EAEQ,WAAWA,GAAkB;AACnC,SAAK;AAAA,MACH,IAAI,YAAY,WAAW;AAAA,QACzB,QAAQ,EAAE,SAAAA,EAAA;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,MAAA,CACX;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,MAAc,iBAAiB;;AAC7B,KAAIR,IAAA,KAAK,cAAL,QAAAA,EAAgB,YAClB,MAAM,KAAK,UAAU,SAAA;AAAA,EAEzB;AAAA,EAEQ,kBAAkBQ,GAAkB;AAC1C,UAAMC,IAAOD,EAAQ,SAAS,SAAS,SAAS;AAChD,WAAOE,qBAAwBD,CAAI;AAAA,EACrC;AAAA,EAEQ,mBAAmBD,GAAkB;AAC3C,UAAMG,IAAWH,EAAQ,YAAY,CAAA;AACrC,WAAOE;AAAA;AAAA;AAAA;AAAA;AAAA,mBAKQC,EAAS,QAAQ,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAM9BA,EAAS,QAAQ,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,EAK/C;AAAA,EAEQ,yBAAyB;AAC/B,WAAOD;AAAA;AAAA;AAAA,gBAGK,KAAK,yBAAyB,iBAAiB,iBAAiB;AAAA;AAAA;AAAA,EAG9E;AAAA,EAEA,SAAS;AACP,UAAME,IAAqB,KAAK,WAAW,KAAK,UAAU,WAAW,GAC/DC,IAAiB,CAAC,KAAK,WAAW,KAAK,kBAAkB,WAAW,KAAK,CAAC,KAAK,SAC/EC,IAAqB,KAAK,0BAA0B,KAAK,kBAAkB,WAAW;AAE5F,WAAOJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAOU,KAAK,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAM/BE,IACEF,2CACAI,IACE,KAAK,uBAAA,IACLD,IACEH,iDACAA;AAAA,sBACI,KAAK,kBAAkB;AAAA,MACvB,CAACF,MAAA;;AAAY,eAAAE;AAAA;AAAA,0CAEKV,IAAA,KAAK,oBAAL,gBAAAA,EAAsB,QAAOQ,EAAQ,KAAK,aAAa,EAAE;AAAA,yCAC1DL,IAAA,KAAK,oBAAL,gBAAAA,EAAsB,QAAOK,EAAQ,KAC9C,kBACA,EAAE;AAAA;AAAA,6CAEWO,IAAA,KAAK,oBAAL,gBAAAA,EAAsB,QAAOP,EAAQ,EAAE;AAAA,mCAC/C,MAAM,KAAK,gBAAgBA,CAAO,CAAC;AAAA,sCAChC,MAAM,KAAK,sBAAsBA,CAAO,CAAC;AAAA;AAAA,mDAE5B,KAAK,kBAAkBA,CAAO,CAAC;AAAA;AAAA;AAAA,gCAGlDA,EAAQ,eAAeA,EAAQ,IAAI;AAAA;AAAA;AAAA,gCAGnCA,EAAQ,QAAQ,SAAS;AAAA;AAAA;AAAA,4BAG7B,KAAK,mBAAmBA,CAAO,CAAC;AAAA;AAAA;AAAA,qCAGvB,CAACQ,MAAa;AACrB,UAAAA,EAAE,gBAAA,GACF,KAAK,WAAWR,CAAO;AAAA,QACzB,CAAC;AAAA,+CACkBA,EAAQ,eAAeA,EAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAM7D;AAAA;AAAA;AAAA,wBAGG,KAAK,WAAW,CAAC,KAAK,yBACpB,KAAK,uBAAA,IACL,IAAI;AAAA;AAAA,mBAEX;AAAA;AAAA;AAAA;AAAA,EAIjB;AACF;AAviBalB,EACJ,SAAS2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqNhBC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GArNf7B,EAsNX,WAAA,aAAA,CAAA;AAMQ4B,EAAA;AAAA,EADPE,EAAA;AAAM,GA3NI9B,EA4NH,WAAA,mBAAA,CAAA;AAMA4B,EAAA;AAAA,EADPE,EAAA;AAAM,GAjOI9B,EAkOH,WAAA,eAAA,CAAA;AAMA4B,EAAA;AAAA,EADPE,EAAA;AAAM,GAvOI9B,EAwOH,WAAA,aAAA,CAAA;AAMA4B,EAAA;AAAA,EADPE,EAAA;AAAM,GA7OI9B,EA8OH,WAAA,WAAA,CAAA;AAMA4B,EAAA;AAAA,EADPE,EAAA;AAAM,GAnPI9B,EAoPH,WAAA,WAAA,CAAA;AAqBA4B,EAAA;AAAA,EADPE,EAAA;AAAM,GAxQI9B,EAyQH,WAAA,0BAAA,CAAA;AAzQGA,IAAN4B,EAAA;AAAA,EADNG,EAAc,cAAc;AAAA,GAChB/B,CAAA;"}
|
|
1
|
+
{"version":3,"file":"sw-directory.js","sources":["../../src/components/sw-directory.ts"],"sourcesContent":["/**\n * Directory Component\n *\n * A searchable list of addresses from the directory service.\n * Supports filtering, selection, and pagination.\n *\n * @example\n * ```html\n * <sw-directory .directory=${directory}></sw-directory>\n * ```\n *\n * @fires sw-address-select - Fired when an address is selected. Detail: `{ address: Address }`\n * @fires sw-dial - Fired when the call button on an address is clicked. Detail: `{ address: Address, channel: string }`\n *\n * @cssprop [--interactive-button-primary-bg=#044ef4] - Primary brand color\n * @cssprop [--interactive-button-primary-hover=#0342cf] - Primary color on hover\n * @cssprop [--interactive-status-success=#22c55e] - Success/positive color\n * @cssprop [--fg-default=#f0f0f4] - Primary text color\n * @cssprop [--fg-muted=#a0a0aa] - Secondary/muted text color\n * @cssprop [--bg-surface=#181a28] - Component background color\n * @cssprop [--bg-surface-raised=#222436] - Background color on hover\n * @cssprop [--interactive-dropdown-hover=#333338] - Background color on active/press\n * @cssprop [--border-default=rgba(255,255,255,0.12)] - Border color\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { Observable } from 'rxjs';\nimport './UI/icons/sw-ui-icon.js';\nimport { hostReset } from './UI/host-reset.js';\n\n/**\n * Address type from SDK\n */\nexport interface Address {\n id: string;\n name: string;\n displayName?: string;\n type?: 'room' | 'person';\n channels?: {\n audio?: boolean;\n video?: boolean;\n messaging?: boolean;\n };\n}\n\n/**\n * Directory interface for component\n */\nexport interface DirectoryService {\n addresses$: Observable<Address[]>;\n loading$?: Observable<boolean>;\n hasMore$?: Observable<boolean>;\n loadMore?(): Promise<void>;\n}\n\n@customElement('sw-directory')\nexport class SwDirectory extends LitElement {\n static styles = [hostReset, css`\n :host {\n display: block;\n font-family: var(--type-family-body);\n color: var(--fg-default);\n }\n\n .container {\n display: flex;\n flex-direction: column;\n background: var(--bg-surface);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n overflow: hidden;\n }\n\n .search {\n display: flex;\n align-items: center;\n gap: var(--sp-2);\n padding: var(--sp-3);\n border-bottom: 1px solid var(--border-default);\n }\n\n .search-input {\n flex: 1;\n padding: var(--sp-2) var(--sp-3);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n background: var(--bg-surface);\n color: var(--fg-default);\n font-family: var(--type-family-body);\n font-size: var(--type-size-small);\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .search-input:focus {\n border-color: var(--interactive-button-primary-bg);\n }\n\n .search-input::placeholder {\n color: var(--fg-muted);\n }\n\n .list {\n display: flex;\n flex-direction: column;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .item {\n display: flex;\n align-items: center;\n gap: var(--sp-3);\n padding: var(--sp-3) var(--sp-4);\n border-bottom: 1px solid var(--border-default);\n cursor: pointer;\n transition: background-color 0.15s ease;\n }\n\n .item:last-child {\n border-bottom: none;\n }\n\n .item:hover {\n background: var(--bg-surface-raised);\n }\n\n .item:active {\n background: var(--interactive-dropdown-hover);\n }\n\n .item.selected {\n background: var(--interactive-button-primary-bg);\n color: white;\n }\n\n .item.selected .item-type,\n .item.selected .item-channels {\n color: rgba(255, 255, 255, 0.8);\n }\n\n .item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: var(--bg-surface-raised);\n color: var(--fg-muted);\n flex-shrink: 0;\n }\n\n .item.selected .item-icon {\n background: rgba(255, 255, 255, 0.2);\n color: white;\n }\n\n .item-icon svg,\n .item-icon sw-ui-icon {\n width: 20px;\n height: 20px;\n }\n\n .item-content {\n flex: 1;\n min-width: 0;\n }\n\n .item-name {\n font-size: var(--type-size-small);\n font-weight: 500;\n color: inherit;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .item-type {\n font-size: var(--type-size-caption);\n color: var(--fg-muted);\n text-transform: capitalize;\n }\n\n .item-channels {\n display: flex;\n gap: var(--sp-1);\n color: var(--fg-muted);\n }\n\n .item-channels sw-ui-icon {\n width: 16px;\n height: 16px;\n }\n\n .item-channels sw-ui-icon.active {\n color: var(--interactive-status-success);\n }\n\n .loading,\n .empty {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--sp-4);\n color: var(--fg-muted);\n font-size: var(--type-size-small);\n }\n\n .scroll-sentinel {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--sp-3);\n min-height: 1px;\n }\n\n .scroll-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sp-2);\n padding: var(--sp-3);\n color: var(--fg-muted);\n font-size: var(--type-size-caption);\n }\n\n .spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--border-default);\n border-top-color: var(--interactive-button-primary-bg);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n .dial-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: var(--interactive-status-success);\n border: none;\n border-radius: 50%;\n color: white;\n cursor: pointer;\n flex-shrink: 0;\n transition: background-color 0.15s ease;\n }\n\n .dial-button:hover {\n background: #0ea472;\n }\n\n .dial-button sw-ui-icon {\n pointer-events: none;\n }\n `];\n\n /**\n * Directory service with addresses$ observable\n */\n @property({ type: Object })\n directory: DirectoryService | null = null;\n\n /**\n * Currently selected address\n */\n @state()\n private selectedAddress: Address | null = null;\n\n /**\n * Search filter query\n */\n @state()\n private searchQuery: string = '';\n\n /**\n * List of addresses from directory\n */\n @state()\n private addresses: Address[] = [];\n\n /**\n * Loading state\n */\n @state()\n private loading: boolean = false;\n\n /**\n * Has more addresses to load\n */\n @state()\n private hasMore: boolean = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Debounce timer for search\n */\n private searchDebounceTimer: number | null = null;\n\n /**\n * IntersectionObserver for infinite scroll\n */\n private intersectionObserver: IntersectionObserver | null = null;\n\n /**\n * Flag to track if we're auto-loading for search\n */\n @state()\n private isAutoLoadingForSearch: boolean = false;\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToDirectory();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n private subscribeToDirectory() {\n if (!this.directory) return;\n\n // Subscribe to addresses\n if (this.directory.addresses$) {\n const addressesSub = this.directory.addresses$.subscribe((addresses) => {\n this.addresses = addresses;\n });\n this.subscriptions.push(addressesSub);\n }\n\n // Subscribe to loading state\n if (this.directory.loading$) {\n const loadingSub = this.directory.loading$.subscribe((loading) => {\n this.loading = loading;\n });\n this.subscriptions.push(loadingSub);\n }\n\n // Subscribe to hasMore\n if (this.directory.hasMore$) {\n const hasMoreSub = this.directory.hasMore$.subscribe((hasMore) => {\n this.hasMore = hasMore;\n });\n this.subscriptions.push(hasMoreSub);\n }\n\n // Load initial data\n if (this.directory.loadMore) {\n this.directory.loadMore();\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n if (this.searchDebounceTimer) {\n clearTimeout(this.searchDebounceTimer);\n }\n if (this.intersectionObserver) {\n this.intersectionObserver.disconnect();\n this.intersectionObserver = null;\n }\n }\n\n protected firstUpdated() {\n this.setupInfiniteScroll();\n }\n\n private setupInfiniteScroll() {\n const sentinel = this.shadowRoot?.querySelector('.scroll-sentinel');\n if (!sentinel) return;\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (entry?.isIntersecting && this.hasMore && !this.loading) {\n this.handleLoadMore();\n }\n },\n {\n root: this.shadowRoot?.querySelector('.list'),\n rootMargin: '100px',\n threshold: 0\n }\n );\n\n this.intersectionObserver.observe(sentinel);\n }\n\n protected updated(changedProperties: Map<string, unknown>) {\n super.updated(changedProperties);\n\n // Handle directory property changes\n if (changedProperties.has('directory')) {\n this.cleanup();\n this.subscribeToDirectory();\n }\n\n // Re-setup infinite scroll if needed\n if (!this.intersectionObserver) {\n this.setupInfiniteScroll();\n }\n\n // Auto-load more when searching and no results found but more available\n // Also check when loading state changes so we can trigger subsequent loads\n if (\n changedProperties.has('addresses') ||\n changedProperties.has('searchQuery') ||\n changedProperties.has('loading')\n ) {\n this.checkAutoLoadForSearch();\n }\n }\n\n private async checkAutoLoadForSearch() {\n // If we have a search query, no filtered results, but more to load, keep loading\n // Note: !this.loading prevents concurrent loads, isAutoLoadingForSearch is for UI state only\n if (\n this.searchQuery.trim() &&\n this.filteredAddresses.length === 0 &&\n this.hasMore &&\n !this.loading\n ) {\n this.isAutoLoadingForSearch = true;\n await this.handleLoadMore();\n // The updated() will be called again when addresses/loading change, continuing the loop\n } else if (this.filteredAddresses.length > 0 || !this.hasMore) {\n this.isAutoLoadingForSearch = false;\n }\n }\n\n private get filteredAddresses(): Address[] {\n if (!this.searchQuery.trim()) {\n return this.addresses;\n }\n const query = this.searchQuery.toLowerCase();\n return this.addresses.filter(\n (addr) =>\n addr.name.toLowerCase().includes(query) ||\n (addr.displayName && addr.displayName.toLowerCase().includes(query))\n );\n }\n\n private handleSearchInput(e: Event) {\n const input = e.target as HTMLInputElement;\n\n // Debounce search\n if (this.searchDebounceTimer) {\n clearTimeout(this.searchDebounceTimer);\n }\n\n this.searchDebounceTimer = window.setTimeout(() => {\n this.searchQuery = input.value;\n }, 200);\n }\n\n private handleItemClick(address: Address) {\n this.selectedAddress = address;\n\n this.dispatchEvent(\n new CustomEvent('sw-address-select', {\n detail: { address },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private handleItemDoubleClick(address: Address) {\n this.handleDial(address);\n }\n\n private handleDial(address: Address) {\n this.dispatchEvent(\n new CustomEvent('sw-dial', {\n detail: { address },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private async handleLoadMore() {\n if (this.directory?.loadMore) {\n await this.directory.loadMore();\n }\n }\n\n private renderAddressIcon(address: Address) {\n const name = address.type === 'room' ? 'room' : 'person';\n return html`<sw-ui-icon name=${name} size=\"20\"></sw-ui-icon>`;\n }\n\n private renderChannelIcons(address: Address) {\n const channels = address.channels || {};\n return html`\n <div class=\"item-channels\">\n <sw-ui-icon\n name=\"mic-on\"\n size=\"16\"\n class=\"${channels.audio ? 'active' : ''}\"\n title=\"Audio\"\n ></sw-ui-icon>\n <sw-ui-icon\n name=\"camera-on\"\n size=\"16\"\n class=\"${channels.video ? 'active' : ''}\"\n title=\"Video\"\n ></sw-ui-icon>\n </div>\n `;\n }\n\n private renderLoadingIndicator() {\n return html`\n <div class=\"scroll-loading\">\n <div class=\"spinner\"></div>\n <span>${this.isAutoLoadingForSearch ? 'Searching...' : 'Loading more...'}</span>\n </div>\n `;\n }\n\n render() {\n const showInitialLoading = this.loading && this.addresses.length === 0;\n const showEmptyState = !this.loading && this.filteredAddresses.length === 0 && !this.hasMore;\n const showSearchingState = this.isAutoLoadingForSearch && this.filteredAddresses.length === 0;\n\n return html`\n <div class=\"container\" part=\"container\">\n <div class=\"search\" part=\"search\">\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search addresses...\"\n @input=${this.handleSearchInput}\n aria-label=\"Search addresses\"\n />\n </div>\n\n <div class=\"list\" part=\"list\" role=\"listbox\">\n ${showInitialLoading\n ? html`<div class=\"loading\">Loading...</div>`\n : showSearchingState\n ? this.renderLoadingIndicator()\n : showEmptyState\n ? html`<div class=\"empty\">No addresses found</div>`\n : html`\n ${this.filteredAddresses.map(\n (address) => html`\n <div\n class=\"item ${this.selectedAddress?.id === address.id ? 'selected' : ''}\"\n part=\"item ${this.selectedAddress?.id === address.id\n ? 'item-selected'\n : ''}\"\n role=\"option\"\n aria-selected=\"${this.selectedAddress?.id === address.id}\"\n @click=${() => this.handleItemClick(address)}\n @dblclick=${() => this.handleItemDoubleClick(address)}\n >\n <div class=\"item-icon\">${this.renderAddressIcon(address)}</div>\n <div class=\"item-content\">\n <div class=\"item-name\" part=\"item-name\">\n ${address.displayName || address.name}\n </div>\n <div class=\"item-type\" part=\"item-type\">\n ${address.type || 'address'}\n </div>\n </div>\n ${this.renderChannelIcons(address)}\n <button\n class=\"dial-button\"\n @click=${(e: Event) => {\n e.stopPropagation();\n this.handleDial(address);\n }}\n aria-label=\"Call ${address.displayName || address.name}\"\n >\n <sw-ui-icon name=\"phone-call\" size=\"18\"></sw-ui-icon>\n </button>\n </div>\n `\n )}\n <!-- Scroll sentinel for infinite scroll -->\n <div class=\"scroll-sentinel\">\n ${this.loading && !this.isAutoLoadingForSearch\n ? this.renderLoadingIndicator()\n : null}\n </div>\n `}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-directory': SwDirectory;\n }\n}\n"],"names":["SwDirectory","LitElement","addressesSub","addresses","loadingSub","loading","hasMoreSub","hasMore","sub","sentinel","_a","entries","entry","_b","changedProperties","query","addr","input","address","name","html","channels","showInitialLoading","showEmptyState","showSearchingState","_c","e","hostReset","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ import { LitElement } from 'lit';
|
|
|
24
24
|
import type { Call } from '../types/index.js';
|
|
25
25
|
import './UI/icons/sw-ui-icon.js';
|
|
26
26
|
export declare class SwParticipantControls extends LitElement {
|
|
27
|
-
static styles: import("lit").CSSResult;
|
|
27
|
+
static styles: import("lit").CSSResult[];
|
|
28
28
|
participantId: string;
|
|
29
29
|
showVolume: boolean;
|
|
30
30
|
showPin: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-participant-controls.d.ts","sourceRoot":"","sources":["../../src/components/sw-participant-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAMrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"sw-participant-controls.d.ts","sourceRoot":"","sources":["../../src/components/sw-participant-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAMrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,0BAA0B,CAAC;AAKlC,qBACa,qBAAsB,SAAQ,UAAU;IACnD,MAAM,CAAC,MAAM,4BAyHV;IAGH,aAAa,SAAM;IAGnB,UAAU,UAAS;IAGnB,OAAO,UAAS;IAIhB,OAAO,CAAC,UAAU,CAAC,CAAY;IAE/B,0EAA0E;IAC9C,IAAI,CAAC,EAAE,IAAI,CAAC;IAE/B,OAAO,CAAC,mBAAmB,CAAyB;IACpD,OAAO,CAAC,mBAAmB,CAAgB;IAEpD,OAAO,CAAC,oBAAoB,CAAsB;IAEzC,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,SAAS,CAAS;IAEnC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAgBtD,oBAAoB;IAQpB,OAAO,KAAK,YAAY,GAGvB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,UAAU,GAErB;YAIa,gBAAgB;YAchB,gBAAgB;YAchB,OAAO;IAUrB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,UAAU;IAalB,MAAM;CAsEP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,yBAAyB,EAAE,qBAAqB,CAAC;KAClD;CACF"}
|
|
@@ -3,10 +3,11 @@ import { property as l, state as c, customElement as v } from "lit/decorators.js
|
|
|
3
3
|
import { consume as g } from "@lit/context";
|
|
4
4
|
import { callStateContext as f } from "../context/call-state-context.js";
|
|
5
5
|
import { getLogger as _ } from "@signalwire/js";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { hostReset as w } from "./UI/host-reset.js";
|
|
7
|
+
var y = Object.defineProperty, x = Object.getOwnPropertyDescriptor, a = (t, i, n, p) => {
|
|
8
|
+
for (var o = p > 1 ? void 0 : p ? x(i, n) : i, u = t.length - 1, d; u >= 0; u--)
|
|
8
9
|
(d = t[u]) && (o = (p ? d(i, n, o) : d(o)) || o);
|
|
9
|
-
return p && o &&
|
|
10
|
+
return p && o && y(i, n, o), o;
|
|
10
11
|
};
|
|
11
12
|
const h = _();
|
|
12
13
|
let e = class extends b {
|
|
@@ -146,7 +147,7 @@ let e = class extends b {
|
|
|
146
147
|
`;
|
|
147
148
|
}
|
|
148
149
|
};
|
|
149
|
-
e.styles = m`
|
|
150
|
+
e.styles = [w, m`
|
|
150
151
|
:host {
|
|
151
152
|
display: block;
|
|
152
153
|
font-family: var(--type-family-body);
|
|
@@ -267,7 +268,7 @@ e.styles = m`
|
|
|
267
268
|
text-align: center;
|
|
268
269
|
padding: 6px;
|
|
269
270
|
}
|
|
270
|
-
|
|
271
|
+
`];
|
|
271
272
|
a([
|
|
272
273
|
l({ type: String, attribute: "participant-id" })
|
|
273
274
|
], e.prototype, "participantId", 2);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-participant-controls.js","sources":["../../src/components/sw-participant-controls.ts"],"sourcesContent":["/**\n * Per-participant control panel.\n *\n * Takes a `participant-id` and looks up the participant.\n * Shows mute audio, mute video, volume, pin, and remove actions based on\n * the `capabilities` list from the call state.\n *\n * Input precedence (most specific wins): `.call` > context.\n *\n * @prop {string} participantId - ID of the participant to control\n * @prop {Call} call - explicit Call object (overrides context)\n * @prop {boolean} showVolume - show volume slider\n * @prop {boolean} showPin - show pin/spotlight button\n *\n * @fires sw-participant-volume-change - Volume slider changed. Detail: `{ participantId: string, volume: number }`.\n * @fires sw-participant-pin-toggle - Pin/unpin clicked. Detail: `{ participantId: string, pinned: boolean }`.\n *\n * Themed via the SignalWire DTCG tokens (`--bg-page`, `--bg-surface`, `--bg-surface-raised`,\n * `--fg-default`, `--border-default`, `--radius-md`, `--shadow-md`, `--type-family-body`,\n * `--type-size-small`, `--interactive-button-destructive-bg`,\n * `--interactive-button-destructive-hover`, `--transition-fast`).\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { CallParticipant } from '@signalwire/js';\nimport { callStateContext, type CallState } from '../context/call-state-context.js';\nimport type { Call } from '../types/index.js';\nimport { getLogger } from '@signalwire/js';\nimport './UI/icons/sw-ui-icon.js';\n\nconst logger = getLogger();\n\n@customElement('sw-participant-controls')\nexport class SwParticipantControls extends LitElement {\n static styles = css`\n :host {\n display: block;\n font-family: var(--type-family-body);\n font-size: var(--type-size-small);\n }\n\n .panel {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n background: var(--bg-page);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n min-width: 180px;\n box-shadow: var(--shadow-md);\n color: var(--fg-default);\n }\n\n .name {\n font-size: 0.8125rem;\n font-weight: 500;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--border-default);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .actions {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n button {\n all: unset;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n border-radius: var(--radius-md);\n font-size: 0.75rem;\n cursor: pointer;\n background: var(--bg-surface);\n color: var(--fg-default);\n transition: background var(--transition-fast);\n }\n\n button:hover {\n background: var(--bg-surface-raised);\n }\n\n button.active {\n background: var(--interactive-button-destructive-bg);\n color: #fff;\n }\n\n button.active:hover {\n background: var(--interactive-button-destructive-hover);\n }\n\n button.danger {\n color: var(--interactive-button-destructive-bg);\n }\n\n button.danger:hover {\n background: var(--interactive-button-destructive-bg);\n color: #fff;\n }\n\n .volume-control {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 4px 0;\n }\n\n .volume-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.7rem;\n color: color-mix(in srgb, var(--fg-default) 50%, transparent);\n }\n\n input[type='range'] {\n width: 100%;\n height: 4px;\n appearance: none;\n background: var(--border-default);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n }\n\n input[type='range']::-webkit-slider-thumb {\n appearance: none;\n width: 12px;\n height: 12px;\n background: #3b82f6;\n border-radius: 50%;\n cursor: pointer;\n }\n\n input[type='range']::-moz-range-thumb {\n width: 12px;\n height: 12px;\n background: #3b82f6;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n }\n\n .empty {\n font-size: 0.7rem;\n color: color-mix(in srgb, var(--fg-default) 50%, transparent);\n text-align: center;\n padding: 6px;\n }\n `;\n\n @property({ type: String, attribute: 'participant-id' })\n participantId = '';\n\n @property({ type: Boolean, reflect: true, attribute: 'show-volume' })\n showVolume = false;\n\n @property({ type: Boolean, reflect: true, attribute: 'show-pin' })\n showPin = false;\n\n @consume({ context: callStateContext, subscribe: true })\n @state()\n private _callState?: CallState;\n\n /** Explicit Call — when set, subscribes directly and bypasses context. */\n @property({ type: Object }) call?: Call;\n\n @state() private _directParticipants: CallParticipant[] = [];\n @state() private _directCapabilities: string[] = [];\n\n private _directSubscriptions: Subscription[] = [];\n\n @state() private _volume = 100;\n @state() private _isPinned = false;\n\n protected updated(changed: Map<string, unknown>): void {\n super.updated(changed);\n if (changed.has('call')) {\n this._directSubscriptions.forEach((s) => s.unsubscribe());\n this._directSubscriptions = [];\n this._directParticipants = [];\n this._directCapabilities = [];\n if (this.call) {\n this._directSubscriptions.push(\n this.call.participants$.subscribe((p) => (this._directParticipants = p)),\n this.call.capabilities$.subscribe((c) => (this._directCapabilities = c))\n );\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._directSubscriptions.forEach((s) => s.unsubscribe());\n this._directSubscriptions = [];\n }\n\n // ── Derived state ──────────────────────────────────────────────────\n\n private get _participant() {\n const participants = this.call ? this._directParticipants : this._callState?.participants ?? [];\n return participants.find((p) => p.id === this.participantId);\n }\n\n private get _capabilities(): string[] {\n return this.call ? this._directCapabilities : this._callState?.capabilities ?? [];\n }\n\n private get _canMuteAudio(): boolean {\n return this._capabilities.includes('memberMuteAudio');\n }\n\n private get _canMuteVideo(): boolean {\n return this._capabilities.includes('memberMuteVideo');\n }\n\n private get _canRemove(): boolean {\n return this._capabilities.includes('memberRemove');\n }\n\n // ── Actions ────────────────────────────────────────────────────────\n\n private async _toggleAudioMute() {\n const p = this._participant;\n if (!p) return;\n try {\n if (p.audioMuted) {\n await p.unmute();\n } else {\n await p.mute();\n }\n } catch (err) {\n logger.error('[ParticipantControls] Toggle audio failed:', err);\n }\n }\n\n private async _toggleVideoMute() {\n const p = this._participant;\n if (!p) return;\n try {\n if (p.videoMuted) {\n await p.unmuteVideo();\n } else {\n await p.muteVideo();\n }\n } catch (err) {\n logger.error('[ParticipantControls] Toggle video failed:', err);\n }\n }\n\n private async _remove() {\n const p = this._participant;\n if (!p?.remove) return;\n try {\n await p.remove();\n } catch (err) {\n logger.error('[ParticipantControls] Remove failed:', err);\n }\n }\n\n private _onVolumeChange(e: Event) {\n this._volume = parseInt((e.target as HTMLInputElement).value, 10);\n this.dispatchEvent(\n new CustomEvent('sw-participant-volume-change', {\n detail: { participantId: this.participantId, volume: this._volume },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private _togglePin() {\n this._isPinned = !this._isPinned;\n this.dispatchEvent(\n new CustomEvent('sw-participant-pin-toggle', {\n detail: { participantId: this.participantId, pinned: this._isPinned },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n // ── Render ─────────────────────────────────────────────────────────\n\n render() {\n const p = this._participant;\n if (!p) return html`<div class=\"panel\"><div class=\"empty\">Participant not found</div></div>`;\n\n const hasActions = this._canMuteAudio || this._canMuteVideo || this._canRemove || this.showVolume || this.showPin;\n\n return html`\n <div class=\"panel\" part=\"panel\">\n <div class=\"name\" part=\"name\">${p.name ?? 'Participant'}</div>\n\n <div class=\"actions\">\n ${!hasActions ? html`<div class=\"empty\">No actions available</div>` : nothing}\n\n ${this._canMuteAudio\n ? html`\n <button class=${p.audioMuted ? 'active' : ''} @click=${this._toggleAudioMute}>\n <sw-ui-icon name=${p.audioMuted ? 'mic-off' : 'mic-on'} size=\"14\"></sw-ui-icon>\n ${p.audioMuted ? 'Unmute' : 'Mute'}\n </button>\n `\n : nothing}\n\n ${this._canMuteVideo\n ? html`\n <button class=${p.videoMuted ? 'active' : ''} @click=${this._toggleVideoMute}>\n <sw-ui-icon name=${p.videoMuted ? 'camera-off' : 'camera-on'} size=\"14\"></sw-ui-icon>\n ${p.videoMuted ? 'Enable video' : 'Disable video'}\n </button>\n `\n : nothing}\n\n ${this.showVolume\n ? html`\n <div class=\"volume-control\">\n <label class=\"volume-label\">\n <sw-ui-icon name=\"speaker-on\" size=\"14\"></sw-ui-icon>\n Volume: ${this._volume}%\n </label>\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n .value=${String(this._volume)}\n @input=${this._onVolumeChange}\n />\n </div>\n `\n : nothing}\n\n ${this.showPin\n ? html`\n <button class=${this._isPinned ? 'active' : ''} @click=${this._togglePin}>\n <sw-ui-icon name=\"settings\" size=\"14\"></sw-ui-icon>\n ${this._isPinned ? 'Unpin' : 'Pin'}\n </button>\n `\n : nothing}\n\n ${this._canRemove\n ? html`\n <button class=\"danger\" @click=${this._remove}>\n <sw-ui-icon name=\"close\" size=\"14\"></sw-ui-icon>\n Remove\n </button>\n `\n : nothing}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-participant-controls': SwParticipantControls;\n }\n}\n"],"names":["logger","getLogger","SwParticipantControls","LitElement","changed","s","p","c","_a","err","e","html","hasActions","nothing","css","__decorateClass","property","consume","callStateContext","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sw-participant-controls.js","sources":["../../src/components/sw-participant-controls.ts"],"sourcesContent":["/**\n * Per-participant control panel.\n *\n * Takes a `participant-id` and looks up the participant.\n * Shows mute audio, mute video, volume, pin, and remove actions based on\n * the `capabilities` list from the call state.\n *\n * Input precedence (most specific wins): `.call` > context.\n *\n * @prop {string} participantId - ID of the participant to control\n * @prop {Call} call - explicit Call object (overrides context)\n * @prop {boolean} showVolume - show volume slider\n * @prop {boolean} showPin - show pin/spotlight button\n *\n * @fires sw-participant-volume-change - Volume slider changed. Detail: `{ participantId: string, volume: number }`.\n * @fires sw-participant-pin-toggle - Pin/unpin clicked. Detail: `{ participantId: string, pinned: boolean }`.\n *\n * Themed via the SignalWire DTCG tokens (`--bg-page`, `--bg-surface`, `--bg-surface-raised`,\n * `--fg-default`, `--border-default`, `--radius-md`, `--shadow-md`, `--type-family-body`,\n * `--type-size-small`, `--interactive-button-destructive-bg`,\n * `--interactive-button-destructive-hover`, `--transition-fast`).\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { CallParticipant } from '@signalwire/js';\nimport { callStateContext, type CallState } from '../context/call-state-context.js';\nimport type { Call } from '../types/index.js';\nimport { getLogger } from '@signalwire/js';\nimport './UI/icons/sw-ui-icon.js';\nimport { hostReset } from './UI/host-reset.js';\n\nconst logger = getLogger();\n\n@customElement('sw-participant-controls')\nexport class SwParticipantControls extends LitElement {\n static styles = [hostReset, css`\n :host {\n display: block;\n font-family: var(--type-family-body);\n font-size: var(--type-size-small);\n }\n\n .panel {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n background: var(--bg-page);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-md);\n min-width: 180px;\n box-shadow: var(--shadow-md);\n color: var(--fg-default);\n }\n\n .name {\n font-size: 0.8125rem;\n font-weight: 500;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--border-default);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .actions {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n button {\n all: unset;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n border-radius: var(--radius-md);\n font-size: 0.75rem;\n cursor: pointer;\n background: var(--bg-surface);\n color: var(--fg-default);\n transition: background var(--transition-fast);\n }\n\n button:hover {\n background: var(--bg-surface-raised);\n }\n\n button.active {\n background: var(--interactive-button-destructive-bg);\n color: #fff;\n }\n\n button.active:hover {\n background: var(--interactive-button-destructive-hover);\n }\n\n button.danger {\n color: var(--interactive-button-destructive-bg);\n }\n\n button.danger:hover {\n background: var(--interactive-button-destructive-bg);\n color: #fff;\n }\n\n .volume-control {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 4px 0;\n }\n\n .volume-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.7rem;\n color: color-mix(in srgb, var(--fg-default) 50%, transparent);\n }\n\n input[type='range'] {\n width: 100%;\n height: 4px;\n appearance: none;\n background: var(--border-default);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n }\n\n input[type='range']::-webkit-slider-thumb {\n appearance: none;\n width: 12px;\n height: 12px;\n background: #3b82f6;\n border-radius: 50%;\n cursor: pointer;\n }\n\n input[type='range']::-moz-range-thumb {\n width: 12px;\n height: 12px;\n background: #3b82f6;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n }\n\n .empty {\n font-size: 0.7rem;\n color: color-mix(in srgb, var(--fg-default) 50%, transparent);\n text-align: center;\n padding: 6px;\n }\n `];\n\n @property({ type: String, attribute: 'participant-id' })\n participantId = '';\n\n @property({ type: Boolean, reflect: true, attribute: 'show-volume' })\n showVolume = false;\n\n @property({ type: Boolean, reflect: true, attribute: 'show-pin' })\n showPin = false;\n\n @consume({ context: callStateContext, subscribe: true })\n @state()\n private _callState?: CallState;\n\n /** Explicit Call — when set, subscribes directly and bypasses context. */\n @property({ type: Object }) call?: Call;\n\n @state() private _directParticipants: CallParticipant[] = [];\n @state() private _directCapabilities: string[] = [];\n\n private _directSubscriptions: Subscription[] = [];\n\n @state() private _volume = 100;\n @state() private _isPinned = false;\n\n protected updated(changed: Map<string, unknown>): void {\n super.updated(changed);\n if (changed.has('call')) {\n this._directSubscriptions.forEach((s) => s.unsubscribe());\n this._directSubscriptions = [];\n this._directParticipants = [];\n this._directCapabilities = [];\n if (this.call) {\n this._directSubscriptions.push(\n this.call.participants$.subscribe((p) => (this._directParticipants = p)),\n this.call.capabilities$.subscribe((c) => (this._directCapabilities = c))\n );\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._directSubscriptions.forEach((s) => s.unsubscribe());\n this._directSubscriptions = [];\n }\n\n // ── Derived state ──────────────────────────────────────────────────\n\n private get _participant() {\n const participants = this.call ? this._directParticipants : this._callState?.participants ?? [];\n return participants.find((p) => p.id === this.participantId);\n }\n\n private get _capabilities(): string[] {\n return this.call ? this._directCapabilities : this._callState?.capabilities ?? [];\n }\n\n private get _canMuteAudio(): boolean {\n return this._capabilities.includes('memberMuteAudio');\n }\n\n private get _canMuteVideo(): boolean {\n return this._capabilities.includes('memberMuteVideo');\n }\n\n private get _canRemove(): boolean {\n return this._capabilities.includes('memberRemove');\n }\n\n // ── Actions ────────────────────────────────────────────────────────\n\n private async _toggleAudioMute() {\n const p = this._participant;\n if (!p) return;\n try {\n if (p.audioMuted) {\n await p.unmute();\n } else {\n await p.mute();\n }\n } catch (err) {\n logger.error('[ParticipantControls] Toggle audio failed:', err);\n }\n }\n\n private async _toggleVideoMute() {\n const p = this._participant;\n if (!p) return;\n try {\n if (p.videoMuted) {\n await p.unmuteVideo();\n } else {\n await p.muteVideo();\n }\n } catch (err) {\n logger.error('[ParticipantControls] Toggle video failed:', err);\n }\n }\n\n private async _remove() {\n const p = this._participant;\n if (!p?.remove) return;\n try {\n await p.remove();\n } catch (err) {\n logger.error('[ParticipantControls] Remove failed:', err);\n }\n }\n\n private _onVolumeChange(e: Event) {\n this._volume = parseInt((e.target as HTMLInputElement).value, 10);\n this.dispatchEvent(\n new CustomEvent('sw-participant-volume-change', {\n detail: { participantId: this.participantId, volume: this._volume },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private _togglePin() {\n this._isPinned = !this._isPinned;\n this.dispatchEvent(\n new CustomEvent('sw-participant-pin-toggle', {\n detail: { participantId: this.participantId, pinned: this._isPinned },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n // ── Render ─────────────────────────────────────────────────────────\n\n render() {\n const p = this._participant;\n if (!p) return html`<div class=\"panel\"><div class=\"empty\">Participant not found</div></div>`;\n\n const hasActions = this._canMuteAudio || this._canMuteVideo || this._canRemove || this.showVolume || this.showPin;\n\n return html`\n <div class=\"panel\" part=\"panel\">\n <div class=\"name\" part=\"name\">${p.name ?? 'Participant'}</div>\n\n <div class=\"actions\">\n ${!hasActions ? html`<div class=\"empty\">No actions available</div>` : nothing}\n\n ${this._canMuteAudio\n ? html`\n <button class=${p.audioMuted ? 'active' : ''} @click=${this._toggleAudioMute}>\n <sw-ui-icon name=${p.audioMuted ? 'mic-off' : 'mic-on'} size=\"14\"></sw-ui-icon>\n ${p.audioMuted ? 'Unmute' : 'Mute'}\n </button>\n `\n : nothing}\n\n ${this._canMuteVideo\n ? html`\n <button class=${p.videoMuted ? 'active' : ''} @click=${this._toggleVideoMute}>\n <sw-ui-icon name=${p.videoMuted ? 'camera-off' : 'camera-on'} size=\"14\"></sw-ui-icon>\n ${p.videoMuted ? 'Enable video' : 'Disable video'}\n </button>\n `\n : nothing}\n\n ${this.showVolume\n ? html`\n <div class=\"volume-control\">\n <label class=\"volume-label\">\n <sw-ui-icon name=\"speaker-on\" size=\"14\"></sw-ui-icon>\n Volume: ${this._volume}%\n </label>\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n .value=${String(this._volume)}\n @input=${this._onVolumeChange}\n />\n </div>\n `\n : nothing}\n\n ${this.showPin\n ? html`\n <button class=${this._isPinned ? 'active' : ''} @click=${this._togglePin}>\n <sw-ui-icon name=\"settings\" size=\"14\"></sw-ui-icon>\n ${this._isPinned ? 'Unpin' : 'Pin'}\n </button>\n `\n : nothing}\n\n ${this._canRemove\n ? html`\n <button class=\"danger\" @click=${this._remove}>\n <sw-ui-icon name=\"close\" size=\"14\"></sw-ui-icon>\n Remove\n </button>\n `\n : nothing}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-participant-controls': SwParticipantControls;\n }\n}\n"],"names":["logger","getLogger","SwParticipantControls","LitElement","changed","s","p","c","_a","err","e","html","hasActions","nothing","hostReset","css","__decorateClass","property","consume","callStateContext","state","customElement"],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { LitElement } from 'lit';
|
|
23
23
|
import type { Call } from '../types/index.js';
|
|
24
24
|
export declare class SwParticipants extends LitElement {
|
|
25
|
-
static styles: import("lit").CSSResult;
|
|
25
|
+
static styles: import("lit").CSSResult[];
|
|
26
26
|
private _callState?;
|
|
27
27
|
/** Explicit Call — when set, subscribes directly and bypasses context. */
|
|
28
28
|
call?: Call;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-participants.d.ts","sourceRoot":"","sources":["../../src/components/sw-participants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sw-participants.d.ts","sourceRoot":"","sources":["../../src/components/sw-participants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAI3D,qBACa,cAAe,SAAQ,UAAU;IAC5C,MAAM,CAAC,MAAM,4BAiHV;IAIH,OAAO,CAAC,UAAU,CAAC,CAAY;IAE/B,0EAA0E;IAC9C,IAAI,CAAC,EAAE,IAAI,CAAC;IAE/B,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,mBAAmB,CAAyB;IACpD,OAAO,CAAC,WAAW,CAAoC;IAEhE,OAAO,CAAC,oBAAoB,CAAsB;IAGlD,OAAO,CAAC,WAAW,CAAuB;IAE1C,OAAO,CAAC,mBAAmB,CAKzB;IAEF,OAAO,CAAC,aAAa,CAInB;IAEF,iBAAiB;IAMjB,oBAAoB;IAOpB,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IActD,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;YAKT,gBAAgB;YAuBhB,gBAAgB;YAuBhB,aAAa;IAqB3B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAsC3B,OAAO,CAAC,cAAc;IAuCtB,MAAM;CAaP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,iBAAiB,EAAE,cAAc,CAAC;KACnC;CACF"}
|
|
@@ -2,12 +2,13 @@ import { LitElement as p, html as l, css as h } from "lit";
|
|
|
2
2
|
import { state as d, property as b, customElement as m } from "lit/decorators.js";
|
|
3
3
|
import { consume as g } from "@lit/context";
|
|
4
4
|
import { callStateContext as _ } from "../context/call-state-context.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { hostReset as v } from "./UI/host-reset.js";
|
|
6
|
+
var f = Object.defineProperty, y = Object.getOwnPropertyDescriptor, a = (e, t, i, s) => {
|
|
7
|
+
for (var r = s > 1 ? void 0 : s ? y(t, i) : t, n = e.length - 1, c; n >= 0; n--)
|
|
8
|
+
(c = e[n]) && (r = (s ? c(t, i, r) : c(r)) || r);
|
|
9
|
+
return s && r && f(t, i, r), r;
|
|
9
10
|
};
|
|
10
|
-
let
|
|
11
|
+
let o = class extends p {
|
|
11
12
|
constructor() {
|
|
12
13
|
super(...arguments), this._directLayoutLayers = [], this._directParticipants = [], this._directSelf = null, this._directSubscriptions = [], this._openMenuId = null, this._handleOutsideClick = (e) => {
|
|
13
14
|
const t = e.target;
|
|
@@ -155,7 +156,7 @@ let n = class extends p {
|
|
|
155
156
|
<button
|
|
156
157
|
class="menu-trigger"
|
|
157
158
|
part="menu-trigger"
|
|
158
|
-
@click=${(
|
|
159
|
+
@click=${(n) => this._toggleMenu(i, n)}
|
|
159
160
|
aria-label="Participant menu"
|
|
160
161
|
aria-expanded="${this._openMenuId === i}"
|
|
161
162
|
aria-haspopup="menu"
|
|
@@ -169,15 +170,15 @@ let n = class extends p {
|
|
|
169
170
|
`;
|
|
170
171
|
}
|
|
171
172
|
render() {
|
|
172
|
-
var s, r,
|
|
173
|
-
const e = this.call ? this._directLayoutLayers : ((s = this._callState) == null ? void 0 : s.layoutLayers) ?? [], t = this.call ? (r = this._directSelf) == null ? void 0 : r.id : (c = (
|
|
173
|
+
var s, r, n, c;
|
|
174
|
+
const e = this.call ? this._directLayoutLayers : ((s = this._callState) == null ? void 0 : s.layoutLayers) ?? [], t = this.call ? (r = this._directSelf) == null ? void 0 : r.id : (c = (n = this._callState) == null ? void 0 : n.self) == null ? void 0 : c.id, i = e.filter((u) => u.member_id).map((u) => this._renderOverlay(u, u.member_id === t));
|
|
174
175
|
return l`
|
|
175
176
|
${i}
|
|
176
177
|
<slot></slot>
|
|
177
178
|
`;
|
|
178
179
|
}
|
|
179
180
|
};
|
|
180
|
-
|
|
181
|
+
o.styles = [v, h`
|
|
181
182
|
:host {
|
|
182
183
|
display: contents;
|
|
183
184
|
}
|
|
@@ -290,30 +291,30 @@ n.styles = h`
|
|
|
290
291
|
.menu-item.danger:hover {
|
|
291
292
|
background: rgba(239, 68, 68, 0.2);
|
|
292
293
|
}
|
|
293
|
-
|
|
294
|
+
`];
|
|
294
295
|
a([
|
|
295
296
|
g({ context: _, subscribe: !0 }),
|
|
296
297
|
d()
|
|
297
|
-
],
|
|
298
|
+
], o.prototype, "_callState", 2);
|
|
298
299
|
a([
|
|
299
300
|
b({ type: Object })
|
|
300
|
-
],
|
|
301
|
+
], o.prototype, "call", 2);
|
|
301
302
|
a([
|
|
302
303
|
d()
|
|
303
|
-
],
|
|
304
|
+
], o.prototype, "_directLayoutLayers", 2);
|
|
304
305
|
a([
|
|
305
306
|
d()
|
|
306
|
-
],
|
|
307
|
+
], o.prototype, "_directParticipants", 2);
|
|
307
308
|
a([
|
|
308
309
|
d()
|
|
309
|
-
],
|
|
310
|
+
], o.prototype, "_directSelf", 2);
|
|
310
311
|
a([
|
|
311
312
|
d()
|
|
312
|
-
],
|
|
313
|
-
|
|
313
|
+
], o.prototype, "_openMenuId", 2);
|
|
314
|
+
o = a([
|
|
314
315
|
m("sw-participants")
|
|
315
|
-
],
|
|
316
|
+
], o);
|
|
316
317
|
export {
|
|
317
|
-
|
|
318
|
+
o as SwParticipants
|
|
318
319
|
};
|
|
319
320
|
//# sourceMappingURL=sw-participants.js.map
|