@signalwire/web-components 1.0.0-dev-20260413184938 → 1.0.0-dev-20260416174405
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/call-controls.d.ts +2 -2
- package/dist/components/call-controls.d.ts.map +1 -1
- package/dist/components/call-controls.js +4 -4
- package/dist/components/call-controls.js.map +1 -1
- package/dist/components/click-to-call.d.ts +1 -1
- package/dist/components/click-to-call.d.ts.map +1 -1
- package/dist/components/click-to-call.js +1 -1
- package/dist/components/click-to-call.js.map +1 -1
- package/dist/components/participant-controls.d.ts +3 -3
- package/dist/components/participant-controls.d.ts.map +1 -1
- package/dist/components/participant-controls.js +3 -3
- package/dist/components/participant-controls.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +9 -33
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -6
- package/dist/types/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -44,8 +44,8 @@ export type ScreenShareStatus = 'inactive' | 'pending' | 'active';
|
|
|
44
44
|
*/
|
|
45
45
|
export interface SelfParticipant {
|
|
46
46
|
id: string;
|
|
47
|
-
audioMuted$: Observable<boolean>;
|
|
48
|
-
videoMuted$: Observable<boolean>;
|
|
47
|
+
audioMuted$: Observable<boolean | undefined>;
|
|
48
|
+
videoMuted$: Observable<boolean | undefined>;
|
|
49
49
|
screenShareStatus$?: Observable<string>;
|
|
50
50
|
mute: () => Promise<void>;
|
|
51
51
|
unmute: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-controls.d.ts","sourceRoot":"","sources":["../../src/components/call-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAGvC;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"call-controls.d.ts","sourceRoot":"","sources":["../../src/components/call-controls.ts"],"names":[],"mappings":"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"}
|
|
@@ -49,11 +49,11 @@ let a = class extends b {
|
|
|
49
49
|
), s = e.pipe(
|
|
50
50
|
d((o) => (this.selfParticipant = o, o.audioMuted$))
|
|
51
51
|
).subscribe((o) => {
|
|
52
|
-
this._audioMuted = o;
|
|
52
|
+
o !== void 0 && (this._audioMuted = o);
|
|
53
53
|
});
|
|
54
54
|
this.subscriptions.push(s);
|
|
55
55
|
const r = e.pipe(d((o) => o.videoMuted$)).subscribe((o) => {
|
|
56
|
-
this._videoMuted = o;
|
|
56
|
+
o !== void 0 && (this._videoMuted = o);
|
|
57
57
|
});
|
|
58
58
|
this.subscriptions.push(r);
|
|
59
59
|
const i = e.pipe(d((o) => o.screenShareStatus$ ?? g("inactive"))).subscribe((o) => {
|
|
@@ -62,11 +62,11 @@ let a = class extends b {
|
|
|
62
62
|
this.subscriptions.push(i);
|
|
63
63
|
} else t.self && (this.selfParticipant = t.self, t.self.audioMuted$ && this.subscriptions.push(
|
|
64
64
|
t.self.audioMuted$.subscribe((e) => {
|
|
65
|
-
this._audioMuted = e;
|
|
65
|
+
e !== void 0 && (this._audioMuted = e);
|
|
66
66
|
})
|
|
67
67
|
), t.self.videoMuted$ && this.subscriptions.push(
|
|
68
68
|
t.self.videoMuted$.subscribe((e) => {
|
|
69
|
-
this._videoMuted = e;
|
|
69
|
+
e !== void 0 && (this._videoMuted = e);
|
|
70
70
|
})
|
|
71
71
|
), t.self.screenShareStatus$ && this.subscriptions.push(
|
|
72
72
|
t.self.screenShareStatus$.subscribe((e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-controls.js","sources":["../../src/components/call-controls.ts"],"sourcesContent":["/**\n * Call Controls Component\n *\n * Responsive button bar for call actions. Displays mute audio, mute video,\n * screen share, and hangup buttons with states reflecting current call state.\n *\n * @example\n * ```html\n * <sw-call-controls .call=${call}></sw-call-controls>\n * ```\n *\n * @fires sw-mute-audio - Fired when the mute audio button is clicked. Detail: `{ muted: boolean }`\n * @fires sw-mute-video - Fired when the mute video button is clicked. Detail: `{ muted: boolean }`\n * @fires sw-screen-share - Fired when the screen share button is clicked. Detail: `{ active: boolean }`\n * @fires sw-hangup - Fired when the hangup button is clicked.\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary accent color.\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover.\n * @cssprop [--sw-color-danger=#ef4444] - Color for destructive actions.\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover.\n * @cssprop [--sw-color-background=#1a1a1a] - Component background.\n * @cssprop [--sw-color-surface=#2a2a2a] - Button surface color.\n * @cssprop [--sw-color-surface-hover=#3a3a3a] - Button surface on hover.\n * @cssprop [--sw-color-text=#ffffff] - Text color.\n * @cssprop [--sw-color-text-muted=#a0a0a0] - Muted text color.\n * @cssprop [--sw-color-border=#404040] - Border color.\n * @cssprop [--sw-color-active=#ef4444] - Active/toggled button color.\n * @cssprop [--sw-border-radius=8px] - Border radius for buttons.\n * @cssprop [--sw-font-family] - Font family stack.\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit.\n * @cssprop [--sw-space-2=8px] - Small spacing.\n * @cssprop [--sw-space-3=12px] - Medium spacing.\n * @cssprop [--sw-space-4=16px] - Standard spacing.\n * @cssprop [--sw-space-6=24px] - Large spacing.\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription, of } from 'rxjs';\nimport { switchMap, filter } from 'rxjs/operators';\nimport type { Observable } from 'rxjs';\nimport { callContext } from '../context/index.js';\n\n/**\n * Screen share status type\n */\nexport type ScreenShareStatus = 'inactive' | 'pending' | 'active';\n\n/**\n * SelfParticipant interface for call controls\n */\nexport interface SelfParticipant {\n id: string;\n audioMuted$: Observable<boolean>;\n videoMuted$: Observable<boolean>;\n screenShareStatus$?: Observable<string>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n muteVideo: () => Promise<void>;\n unmuteVideo: () => Promise<void>;\n startScreenShare?: () => Promise<void>;\n stopScreenShare?: () => Promise<void>;\n}\n\n/**\n * Call interface for call controls\n */\nexport interface CallControlsCall {\n self?: SelfParticipant | null;\n self$?: Observable<SelfParticipant | null>;\n capabilities$?: Observable<string[]>;\n hangup: () => Promise<void>;\n}\n\n/**\n * Button type for controls\n */\ntype ControlButtonType = 'mute-audio' | 'mute-video' | 'screen-share' | 'hangup';\n\n@customElement('sw-call-controls')\nexport class CallControls extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-background: #1a1a1a;\n --sw-color-surface: #2a2a2a;\n --sw-color-surface-hover: #3a3a3a;\n --sw-color-text: #ffffff;\n --sw-color-text-muted: #a0a0a0;\n --sw-color-border: #404040;\n --sw-color-active: #ef4444;\n --sw-border-radius: 8px;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-space-6: 24px;\n\n display: block;\n font-family: var(--sw-font-family);\n color: var(--sw-color-text);\n }\n\n .container {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-3);\n padding: var(--sw-space-3);\n background: var(--sw-color-background);\n border-radius: var(--sw-border-radius);\n flex-wrap: wrap;\n }\n\n .container.vertical {\n flex-direction: column;\n }\n\n .control-button {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: 50%;\n color: var(--sw-color-text);\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n transform 0.1s ease;\n }\n\n .control-button:hover:not(:disabled) {\n background: var(--sw-color-surface-hover);\n border-color: var(--sw-color-text-muted);\n }\n\n .control-button:focus {\n outline: none;\n box-shadow:\n 0 0 0 2px var(--sw-color-background),\n 0 0 0 4px var(--sw-color-primary);\n }\n\n .control-button:focus-visible {\n outline: 2px solid var(--sw-color-primary);\n outline-offset: 2px;\n }\n\n .control-button:active:not(:disabled) {\n transform: scale(0.95);\n }\n\n .control-button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .control-button.active {\n background: var(--sw-color-active);\n border-color: var(--sw-color-active);\n color: white;\n }\n\n .control-button.active:hover:not(:disabled) {\n background: var(--sw-color-danger-hover);\n border-color: var(--sw-color-danger-hover);\n }\n\n .control-button.hangup {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: white;\n }\n\n .control-button.hangup:hover:not(:disabled) {\n background: var(--sw-color-danger-hover);\n border-color: var(--sw-color-danger-hover);\n }\n\n .control-button.screen-share.active {\n background: var(--sw-color-primary);\n border-color: var(--sw-color-primary);\n }\n\n .control-button.screen-share.active:hover:not(:disabled) {\n background: var(--sw-color-primary-hover);\n border-color: var(--sw-color-primary-hover);\n }\n\n .button-icon {\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n }\n\n .tooltip {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n padding: var(--sw-space-1) var(--sw-space-2);\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 0.2s ease,\n visibility 0.2s ease;\n pointer-events: none;\n z-index: 10;\n }\n\n .tooltip::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: var(--sw-color-border);\n }\n\n .control-button:hover .tooltip,\n .control-button:focus .tooltip {\n opacity: 1;\n visibility: visible;\n }\n\n /* Vertical orientation tooltip positioning */\n .container.vertical .tooltip {\n bottom: auto;\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n }\n\n .container.vertical .tooltip::after {\n top: 50%;\n left: auto;\n right: 100%;\n transform: translateY(-50%);\n border: 6px solid transparent;\n border-right-color: var(--sw-color-border);\n }\n\n /* Overflow menu for responsive collapse */\n .overflow-menu {\n position: relative;\n }\n\n .overflow-button {\n display: none;\n }\n\n .overflow-content {\n display: none;\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n padding: var(--sw-space-2);\n gap: var(--sw-space-2);\n flex-direction: column;\n z-index: 20;\n }\n\n .overflow-menu.open .overflow-content {\n display: flex;\n }\n\n /* Responsive collapse at small widths */\n @media (max-width: 320px) {\n .container:not(.vertical) > .control-button:not(.hangup):not(.overflow-button) {\n display: none;\n }\n\n .container:not(.vertical) .overflow-button {\n display: flex;\n }\n\n /* Ensure overflow content buttons remain visible when menu is open */\n .container:not(.vertical) .overflow-content .control-button {\n display: flex;\n }\n }\n\n /* Dark mode adjustments */\n @media (prefers-color-scheme: light) {\n :host {\n --sw-color-background: #ffffff;\n --sw-color-surface: #f5f5f5;\n --sw-color-surface-hover: #e5e5e5;\n --sw-color-text: #1a1a1a;\n --sw-color-text-muted: #666666;\n --sw-color-border: #d4d4d4;\n }\n }\n\n :host([data-theme='light']) {\n --sw-color-background: #ffffff;\n --sw-color-surface: #f5f5f5;\n --sw-color-surface-hover: #e5e5e5;\n --sw-color-text: #1a1a1a;\n --sw-color-text-muted: #666666;\n --sw-color-border: #d4d4d4;\n }\n `;\n\n /**\n * The call object - can be provided via property or context\n */\n @property({ type: Object }) call?: CallControlsCall;\n\n /**\n * Consume call from context if not provided as property\n */\n @consume({ context: callContext, subscribe: true })\n @state()\n private _contextCall?: CallControlsCall;\n\n /** Layout orientation: 'horizontal' or 'vertical'. */\n @property({ type: String }) orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n /** Whether to display tooltip labels on buttons. */\n @property({ type: Boolean, attribute: 'show-tooltips' }) showTooltips = true;\n\n /**\n * Current audio muted state\n */\n @state() private _audioMuted = false;\n\n /**\n * Current video muted state\n */\n @state() private _videoMuted = false;\n\n /**\n * Current screen share status\n */\n @state() private _screenShareStatus: ScreenShareStatus = 'inactive';\n\n /**\n * Available capabilities\n */\n @state() private _capabilities: string[] = [];\n\n /**\n * Is overflow menu open\n */\n @state() private _overflowOpen = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Cached self participant reference for button handlers.\n * Marked as @state() to trigger re-render when self becomes available,\n * enabling the control buttons.\n */\n @state() private selfParticipant?: SelfParticipant;\n\n /**\n * Get the effective call (property or context)\n */\n private get effectiveCall(): CallControlsCall | undefined {\n return this.call || this._contextCall;\n }\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('call') || changedProperties.has('_contextCall')) {\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n const call = this.effectiveCall;\n if (!call) return;\n\n // Use self$ observable if available (recommended for real calls)\n if (call.self$) {\n // Subscribe to self$ and switch to audioMuted$ when self becomes available\n const nonNullSelf$ = call.self$.pipe(\n filter((self): self is SelfParticipant => self !== null)\n );\n\n const audioSub = nonNullSelf$\n .pipe(\n switchMap((self) => {\n this.selfParticipant = self;\n return self.audioMuted$;\n })\n )\n .subscribe((muted) => {\n this._audioMuted = muted;\n });\n this.subscriptions.push(audioSub);\n\n // Subscribe to videoMuted$\n const videoSub = nonNullSelf$\n .pipe(switchMap((self) => self.videoMuted$))\n .subscribe((muted) => {\n this._videoMuted = muted;\n });\n this.subscriptions.push(videoSub);\n\n // Subscribe to screenShareStatus$\n const screenShareSub = nonNullSelf$\n .pipe(switchMap((self) => self.screenShareStatus$ ?? of('inactive' as ScreenShareStatus)))\n .subscribe((status) => {\n this._screenShareStatus = status as ScreenShareStatus;\n });\n this.subscriptions.push(screenShareSub);\n } else if (call.self) {\n // Fallback for synchronous self (tests, mocks)\n this.selfParticipant = call.self;\n\n if (call.self.audioMuted$) {\n this.subscriptions.push(\n call.self.audioMuted$.subscribe((muted) => {\n this._audioMuted = muted;\n })\n );\n }\n if (call.self.videoMuted$) {\n this.subscriptions.push(\n call.self.videoMuted$.subscribe((muted) => {\n this._videoMuted = muted;\n })\n );\n }\n if (call.self.screenShareStatus$) {\n this.subscriptions.push(\n call.self.screenShareStatus$.subscribe((status) => {\n this._screenShareStatus = status as ScreenShareStatus;\n })\n );\n }\n }\n\n // Subscribe to capabilities\n if (call.capabilities$) {\n this.subscriptions.push(\n call.capabilities$.subscribe((caps) => {\n this._capabilities = caps;\n })\n );\n }\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.selfParticipant = undefined;\n }\n\n /**\n * Check if capability is available\n */\n private hasCapability(capability: string): boolean {\n // If no capabilities defined, assume all available\n if (!this._capabilities || this._capabilities.length === 0) {\n return true;\n }\n return this._capabilities.includes(capability);\n }\n\n /**\n * Handle mute audio toggle\n */\n private async handleMuteAudio(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeMuted = !this._audioMuted;\n\n try {\n if (this._audioMuted) {\n await self.unmute();\n } else {\n await self.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-audio', {\n detail: { muted: willBeMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle audio mute:', error);\n }\n }\n\n /**\n * Handle mute video toggle\n */\n private async handleMuteVideo(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeMuted = !this._videoMuted;\n\n try {\n if (this._videoMuted) {\n await self.unmuteVideo();\n } else {\n await self.muteVideo();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-video', {\n detail: { muted: willBeMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle video mute:', error);\n }\n }\n\n /**\n * Handle screen share toggle\n */\n private async handleScreenShare(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeActive = this._screenShareStatus !== 'active';\n\n try {\n if (this._screenShareStatus === 'active') {\n await self.stopScreenShare?.();\n } else {\n await self.startScreenShare?.();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-screen-share', {\n detail: { active: willBeActive },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle screen share:', error);\n }\n }\n\n /**\n * Handle hangup\n */\n private async handleHangup(): Promise<void> {\n const call = this.effectiveCall;\n if (!call) return;\n\n try {\n await call.hangup();\n\n this.dispatchEvent(\n new CustomEvent('sw-hangup', {\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to hangup:', error);\n }\n }\n\n /**\n * Toggle overflow menu\n */\n private toggleOverflow(): void {\n this._overflowOpen = !this._overflowOpen;\n }\n\n /**\n * Get tooltip text for button\n */\n private getTooltipText(type: ControlButtonType): string {\n switch (type) {\n case 'mute-audio':\n return this._audioMuted ? 'Unmute Audio' : 'Mute Audio';\n case 'mute-video':\n return this._videoMuted ? 'Turn On Camera' : 'Turn Off Camera';\n case 'screen-share':\n return this._screenShareStatus === 'active' ? 'Stop Sharing' : 'Share Screen';\n case 'hangup':\n return 'End Call';\n }\n }\n\n /**\n * Render button icon\n */\n private renderIcon(type: ControlButtonType) {\n switch (type) {\n case 'mute-audio':\n return this._audioMuted\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z\"\n />\n </svg>`;\n\n case 'mute-video':\n return this._videoMuted\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`;\n\n case 'screen-share':\n return this._screenShareStatus === 'active'\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M21.22 18.02l2 2H24v-2h-2.78zm.77-2l.01-10c0-1.11-.9-2-2-2H7.22l5.23 5.23c.18-.04.36-.07.55-.1V7.02l4 3.73-1.58 1.47 5.54 5.54c.61-.33 1.03-.99 1.03-1.74zM2.39 1.73L1.11 3l1.54 1.54c-.4.36-.65.89-.65 1.48v10c0 1.1.89 2 2 2H0v2h18.13l2.71 2.71 1.27-1.27L2.39 1.73zM7 15.02c.31-1.48.92-2.95 2.07-4.06l1.59 1.59c-1.54.38-2.7 1.18-3.66 2.47z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6zm9 9v-4h3l-4-4-4 4h3v4h2z\"\n />\n </svg>`;\n\n case 'hangup':\n return html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z\"\n />\n </svg>`;\n }\n }\n\n /**\n * Render a control button\n */\n private renderButton(\n type: ControlButtonType,\n options: {\n onClick: () => void;\n active?: boolean;\n disabled?: boolean;\n className?: string;\n }\n ) {\n const { onClick, active = false, disabled = false, className = '' } = options;\n const tooltipText = this.getTooltipText(type);\n\n return html`\n <button\n class=\"control-button ${type} ${active ? 'active' : ''} ${className}\"\n part=\"button ${active ? 'button-active' : ''} ${disabled ? 'button-disabled' : ''}\"\n aria-label=\"${tooltipText}\"\n aria-pressed=\"${type !== 'hangup' ? active : nothing}\"\n ?disabled=${disabled}\n @click=${onClick}\n >\n ${this.renderIcon(type)}\n ${this.showTooltips\n ? html` <span class=\"tooltip\" part=\"tooltip\">${tooltipText}</span> `\n : nothing}\n </button>\n `;\n }\n\n /**\n * Render the component\n */\n render() {\n const call = this.effectiveCall;\n const hasSelf = Boolean(this.selfParticipant ?? call?.self);\n\n // Check capabilities - server sends: self, member, vmuted, layout, digit, screenshare, device, lock, end\n // Having 'self' capability implies self-controls are available (mute audio/video)\n const hasSelfCapability = this.hasCapability('self');\n const canMuteAudio =\n hasSelfCapability || this.hasCapability('selfMuteAudio') || this.hasCapability('muteAudio');\n const canMuteVideo =\n hasSelfCapability ||\n this.hasCapability('vmuted') ||\n this.hasCapability('selfMuteVideo') ||\n this.hasCapability('muteVideo');\n const canScreenShare =\n (this.hasCapability('screenshare') || this.hasCapability('screenShare')) &&\n Boolean(this.selfParticipant?.startScreenShare ?? call?.self?.startScreenShare);\n const canHangup = true; // Always allow hangup\n\n return html`\n <div\n class=\"container ${this.orientation === 'vertical' ? 'vertical' : ''}\"\n part=\"container\"\n role=\"toolbar\"\n aria-label=\"Call controls\"\n >\n ${this.renderButton('mute-audio', {\n onClick: () => this.handleMuteAudio(),\n active: this._audioMuted,\n disabled: !hasSelf || !canMuteAudio\n })}\n ${this.renderButton('mute-video', {\n onClick: () => this.handleMuteVideo(),\n active: this._videoMuted,\n disabled: !hasSelf || !canMuteVideo\n })}\n ${this.renderButton('screen-share', {\n onClick: () => this.handleScreenShare(),\n active: this._screenShareStatus === 'active',\n disabled: !hasSelf || !canScreenShare\n })}\n ${this.renderButton('hangup', {\n onClick: () => this.handleHangup(),\n disabled: !call || !canHangup,\n className: 'hangup'\n })}\n\n <!-- Overflow menu for responsive collapse -->\n <div class=\"overflow-menu ${this._overflowOpen ? 'open' : ''}\">\n <button\n class=\"control-button overflow-button\"\n aria-label=\"More options\"\n aria-expanded=\"${this._overflowOpen}\"\n @click=${this.toggleOverflow}\n >\n <svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n />\n </svg>\n </button>\n <div class=\"overflow-content\">\n ${this.renderButton('mute-audio', {\n onClick: () => this.handleMuteAudio(),\n active: this._audioMuted,\n disabled: !hasSelf || !canMuteAudio\n })}\n ${this.renderButton('mute-video', {\n onClick: () => this.handleMuteVideo(),\n active: this._videoMuted,\n disabled: !hasSelf || !canMuteVideo\n })}\n ${this.renderButton('screen-share', {\n onClick: () => this.handleScreenShare(),\n active: this._screenShareStatus === 'active',\n disabled: !hasSelf || !canScreenShare\n })}\n </div>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-controls': CallControls;\n }\n}\n"],"names":["CallControls","LitElement","changedProperties","call","nonNullSelf$","filter","self","audioSub","switchMap","muted","videoSub","screenShareSub","of","status","caps","sub","capability","_a","willBeMuted","error","willBeActive","_b","_c","type","html","options","onClick","active","disabled","className","tooltipText","nothing","hasSelf","hasSelfCapability","canMuteAudio","canMuteVideo","canScreenShare","css","__decorateClass","property","consume","callContext","state","customElement"],"mappings":";;;;;;;;;;;AAiFO,IAAMA,IAAN,cAA2BC,EAAW;AAAA,EAAtC,cAAA;AAAA,UAAA,GAAA,SAAA,GAmQuB,KAAA,cAAyC,cAGZ,KAAA,eAAe,IAK/D,KAAQ,cAAc,IAKtB,KAAQ,cAAc,IAKtB,KAAQ,qBAAwC,YAKhD,KAAQ,gBAA0B,CAAA,GAKlC,KAAQ,gBAAgB,IAKjC,KAAQ,gBAAgC,CAAA;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA,EAYzC,IAAY,gBAA8C;AACxD,WAAO,KAAK,QAAQ,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB;AAClB,UAAM,kBAAA,GACN,KAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKU,QAAQC,GAA+C;AAC/D,UAAM,QAAQA,CAAiB,IAC3BA,EAAkB,IAAI,MAAM,KAAKA,EAAkB,IAAI,cAAc,OACvE,KAAK,qBAAA,GACL,KAAK,mBAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB;AACrB,UAAM,qBAAA,GACN,KAAK,qBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AACjC,UAAMC,IAAO,KAAK;AAClB,QAAKA,GAGL;AAAA,UAAIA,EAAK,OAAO;AAEd,cAAMC,IAAeD,EAAK,MAAM;AAAA,UAC9BE,EAAO,CAACC,MAAkCA,MAAS,IAAI;AAAA,QAAA,GAGnDC,IAAWH,EACd;AAAA,UACCI,EAAU,CAACF,OACT,KAAK,kBAAkBA,GAChBA,EAAK,YACb;AAAA,QAAA,EAEF,UAAU,CAACG,MAAU;AACpB,eAAK,cAAcA;AAAA,QACrB,CAAC;AACH,aAAK,cAAc,KAAKF,CAAQ;AAGhC,cAAMG,IAAWN,EACd,KAAKI,EAAU,CAACF,MAASA,EAAK,WAAW,CAAC,EAC1C,UAAU,CAACG,MAAU;AACpB,eAAK,cAAcA;AAAA,QACrB,CAAC;AACH,aAAK,cAAc,KAAKC,CAAQ;AAGhC,cAAMC,IAAiBP,EACpB,KAAKI,EAAU,CAACF,MAASA,EAAK,sBAAsBM,EAAG,UAA+B,CAAC,CAAC,EACxF,UAAU,CAACC,MAAW;AACrB,eAAK,qBAAqBA;AAAA,QAC5B,CAAC;AACH,aAAK,cAAc,KAAKF,CAAc;AAAA,MACxC,MAAA,CAAWR,EAAK,SAEd,KAAK,kBAAkBA,EAAK,MAExBA,EAAK,KAAK,eACZ,KAAK,cAAc;AAAA,QACjBA,EAAK,KAAK,YAAY,UAAU,CAACM,MAAU;AACzC,eAAK,cAAcA;AAAA,QACrB,CAAC;AAAA,MAAA,GAGDN,EAAK,KAAK,eACZ,KAAK,cAAc;AAAA,QACjBA,EAAK,KAAK,YAAY,UAAU,CAACM,MAAU;AACzC,eAAK,cAAcA;AAAA,QACrB,CAAC;AAAA,MAAA,GAGDN,EAAK,KAAK,sBACZ,KAAK,cAAc;AAAA,QACjBA,EAAK,KAAK,mBAAmB,UAAU,CAACU,MAAW;AACjD,eAAK,qBAAqBA;AAAA,QAC5B,CAAC;AAAA,MAAA;AAMP,MAAIV,EAAK,iBACP,KAAK,cAAc;AAAA,QACjBA,EAAK,cAAc,UAAU,CAACW,MAAS;AACrC,eAAK,gBAAgBA;AAAA,QACvB,CAAC;AAAA,MAAA;AAAA;AAAA,EAGP;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAA6B;AACnC,SAAK,cAAc,QAAQ,CAACC,MAAQA,EAAI,aAAa,GACrD,KAAK,gBAAgB,CAAA,GACrB,KAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAcC,GAA6B;AAEjD,WAAI,CAAC,KAAK,iBAAiB,KAAK,cAAc,WAAW,IAChD,KAEF,KAAK,cAAc,SAASA,CAAU;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAiC;;AAC7C,UAAMV,IAAO,KAAK,qBAAmBW,IAAA,KAAK,kBAAL,gBAAAA,EAAoB;AACzD,QAAI,CAACX,EAAM;AAGX,UAAMY,IAAc,CAAC,KAAK;AAE1B,QAAI;AACF,MAAI,KAAK,cACP,MAAMZ,EAAK,OAAA,IAEX,MAAMA,EAAK,KAAA,GAGb,KAAK;AAAA,QACH,IAAI,YAAY,iBAAiB;AAAA,UAC/B,QAAQ,EAAE,OAAOY,EAAA;AAAA,UACjB,SAAS;AAAA,UACT,UAAU;AAAA,QAAA,CACX;AAAA,MAAA;AAAA,IAEL,SAASC,GAAO;AACd,cAAQ,MAAM,gCAAgCA,CAAK;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAiC;;AAC7C,UAAMb,IAAO,KAAK,qBAAmBW,IAAA,KAAK,kBAAL,gBAAAA,EAAoB;AACzD,QAAI,CAACX,EAAM;AAGX,UAAMY,IAAc,CAAC,KAAK;AAE1B,QAAI;AACF,MAAI,KAAK,cACP,MAAMZ,EAAK,YAAA,IAEX,MAAMA,EAAK,UAAA,GAGb,KAAK;AAAA,QACH,IAAI,YAAY,iBAAiB;AAAA,UAC/B,QAAQ,EAAE,OAAOY,EAAA;AAAA,UACjB,SAAS;AAAA,UACT,UAAU;AAAA,QAAA,CACX;AAAA,MAAA;AAAA,IAEL,SAASC,GAAO;AACd,cAAQ,MAAM,gCAAgCA,CAAK;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,oBAAmC;;AAC/C,UAAMb,IAAO,KAAK,qBAAmBW,IAAA,KAAK,kBAAL,gBAAAA,EAAoB;AACzD,QAAI,CAACX,EAAM;AAGX,UAAMc,IAAe,KAAK,uBAAuB;AAEjD,QAAI;AACF,MAAI,KAAK,uBAAuB,WAC9B,QAAMC,IAAAf,EAAK,oBAAL,gBAAAe,EAAA,KAAAf,MAEN,QAAMgB,IAAAhB,EAAK,qBAAL,gBAAAgB,EAAA,KAAAhB,KAGR,KAAK;AAAA,QACH,IAAI,YAAY,mBAAmB;AAAA,UACjC,QAAQ,EAAE,QAAQc,EAAA;AAAA,UAClB,SAAS;AAAA,UACT,UAAU;AAAA,QAAA,CACX;AAAA,MAAA;AAAA,IAEL,SAASD,GAAO;AACd,cAAQ,MAAM,kCAAkCA,CAAK;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAA8B;AAC1C,UAAMhB,IAAO,KAAK;AAClB,QAAKA;AAEL,UAAI;AACF,cAAMA,EAAK,OAAA,GAEX,KAAK;AAAA,UACH,IAAI,YAAY,aAAa;AAAA,YAC3B,SAAS;AAAA,YACT,UAAU;AAAA,UAAA,CACX;AAAA,QAAA;AAAA,MAEL,SAASgB,GAAO;AACd,gBAAQ,MAAM,qBAAqBA,CAAK;AAAA,MAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAuB;AAC7B,SAAK,gBAAgB,CAAC,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAeI,GAAiC;AACtD,YAAQA,GAAA;AAAA,MACN,KAAK;AACH,eAAO,KAAK,cAAc,iBAAiB;AAAA,MAC7C,KAAK;AACH,eAAO,KAAK,cAAc,mBAAmB;AAAA,MAC/C,KAAK;AACH,eAAO,KAAK,uBAAuB,WAAW,iBAAiB;AAAA,MACjE,KAAK;AACH,eAAO;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAWA,GAAyB;AAC1C,YAAQA,GAAA;AAAA,MACN,KAAK;AACH,eAAO,KAAK,cACRC;AAAA;AAAA;AAAA;AAAA,sBAKAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMN,KAAK;AACH,eAAO,KAAK,cACRA;AAAA;AAAA;AAAA;AAAA,sBAKAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMN,KAAK;AACH,eAAO,KAAK,uBAAuB,WAC/BA;AAAA;AAAA;AAAA;AAAA,sBAKAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMN,KAAK;AACH,eAAOA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA;AAAA,EAMb;AAAA;AAAA;AAAA;AAAA,EAKQ,aACND,GACAE,GAMA;AACA,UAAM,EAAE,SAAAC,GAAS,QAAAC,IAAS,IAAO,UAAAC,IAAW,IAAO,WAAAC,IAAY,OAAOJ,GAChEK,IAAc,KAAK,eAAeP,CAAI;AAE5C,WAAOC;AAAA;AAAA,gCAEqBD,CAAI,IAAII,IAAS,WAAW,EAAE,IAAIE,CAAS;AAAA,uBACpDF,IAAS,kBAAkB,EAAE,IAAIC,IAAW,oBAAoB,EAAE;AAAA,sBACnEE,CAAW;AAAA,wBACTP,MAAS,WAAWI,IAASI,CAAO;AAAA,oBACxCH,CAAQ;AAAA,iBACXF,CAAO;AAAA;AAAA,UAEd,KAAK,WAAWH,CAAI,CAAC;AAAA,UACrB,KAAK,eACHC,0CAA6CM,CAAW,aACxDC,CAAO;AAAA;AAAA;AAAA,EAGjB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS;;AACP,UAAM5B,IAAO,KAAK,eACZ6B,IAAU,GAAQ,KAAK,oBAAmB7B,KAAA,gBAAAA,EAAM,QAIhD8B,IAAoB,KAAK,cAAc,MAAM,GAC7CC,IACJD,KAAqB,KAAK,cAAc,eAAe,KAAK,KAAK,cAAc,WAAW,GACtFE,IACJF,KACA,KAAK,cAAc,QAAQ,KAC3B,KAAK,cAAc,eAAe,KAClC,KAAK,cAAc,WAAW,GAC1BG,KACH,KAAK,cAAc,aAAa,KAAK,KAAK,cAAc,aAAa,MACtE,KAAQnB,IAAA,KAAK,oBAAL,gBAAAA,EAAsB,uBAAoBI,IAAAlB,KAAA,gBAAAA,EAAM,SAAN,gBAAAkB,EAAY;AAGhE,WAAOG;AAAA;AAAA,2BAEgB,KAAK,gBAAgB,aAAa,aAAa,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,UAKlE,KAAK,aAAa,cAAc;AAAA,MAChC,SAAS,MAAM,KAAK,gBAAA;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,UAAU,CAACQ,KAAW,CAACE;AAAA,IAAA,CACxB,CAAC;AAAA,UACA,KAAK,aAAa,cAAc;AAAA,MAChC,SAAS,MAAM,KAAK,gBAAA;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,UAAU,CAACF,KAAW,CAACG;AAAA,IAAA,CACxB,CAAC;AAAA,UACA,KAAK,aAAa,gBAAgB;AAAA,MAClC,SAAS,MAAM,KAAK,kBAAA;AAAA,MACpB,QAAQ,KAAK,uBAAuB;AAAA,MACpC,UAAU,CAACH,KAAW,CAACI;AAAA,IAAA,CACxB,CAAC;AAAA,UACA,KAAK,aAAa,UAAU;AAAA,MAC5B,SAAS,MAAM,KAAK,aAAA;AAAA,MACpB,UAAU,CAACjC,KAAQ;AAAA,MACnB,WAAW;AAAA,IAAA,CACZ,CAAC;AAAA;AAAA;AAAA,oCAG0B,KAAK,gBAAgB,SAAS,EAAE;AAAA;AAAA;AAAA;AAAA,6BAIvC,KAAK,aAAa;AAAA,qBAC1B,KAAK,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAS1B,KAAK,aAAa,cAAc;AAAA,MAChC,SAAS,MAAM,KAAK,gBAAA;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,UAAU,CAAC6B,KAAW,CAACE;AAAA,IAAA,CACxB,CAAC;AAAA,cACA,KAAK,aAAa,cAAc;AAAA,MAChC,SAAS,MAAM,KAAK,gBAAA;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,UAAU,CAACF,KAAW,CAACG;AAAA,IAAA,CACxB,CAAC;AAAA,cACA,KAAK,aAAa,gBAAgB;AAAA,MAClC,SAAS,MAAM,KAAK,kBAAA;AAAA,MACpB,QAAQ,KAAK,uBAAuB;AAAA,MACpC,UAAU,CAACH,KAAW,CAACI;AAAA,IAAA,CACxB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKZ;AACF;AA9tBapC,EACJ,SAASqC;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;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;AAwPYC,EAAA;AAAA,EAA3BC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAzPfvC,EAyPiB,WAAA,QAAA,CAAA;AAOpBsC,EAAA;AAAA,EAFPE,EAAQ,EAAE,SAASC,GAAa,WAAW,IAAM;AAAA,EACjDC,EAAA;AAAM,GA/PI1C,EAgQH,WAAA,gBAAA,CAAA;AAGoBsC,EAAA;AAAA,EAA3BC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAnQfvC,EAmQiB,WAAA,eAAA,CAAA;AAG6BsC,EAAA;AAAA,EAAxDC,EAAS,EAAE,MAAM,SAAS,WAAW,iBAAiB;AAAA,GAtQ5CvC,EAsQ8C,WAAA,gBAAA,CAAA;AAKxCsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GA3QI1C,EA2QM,WAAA,eAAA,CAAA;AAKAsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GAhRI1C,EAgRM,WAAA,eAAA,CAAA;AAKAsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GArRI1C,EAqRM,WAAA,sBAAA,CAAA;AAKAsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GA1RI1C,EA0RM,WAAA,iBAAA,CAAA;AAKAsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GA/RI1C,EA+RM,WAAA,iBAAA,CAAA;AAYAsC,EAAA;AAAA,EAAhBI,EAAA;AAAM,GA3SI1C,EA2SM,WAAA,mBAAA,CAAA;AA3SNA,IAANsC,EAAA;AAAA,EADNK,EAAc,kBAAkB;AAAA,GACpB3C,CAAA;"}
|
|
1
|
+
{"version":3,"file":"call-controls.js","sources":["../../src/components/call-controls.ts"],"sourcesContent":["/**\n * Call Controls Component\n *\n * Responsive button bar for call actions. Displays mute audio, mute video,\n * screen share, and hangup buttons with states reflecting current call state.\n *\n * @example\n * ```html\n * <sw-call-controls .call=${call}></sw-call-controls>\n * ```\n *\n * @fires sw-mute-audio - Fired when the mute audio button is clicked. Detail: `{ muted: boolean }`\n * @fires sw-mute-video - Fired when the mute video button is clicked. Detail: `{ muted: boolean }`\n * @fires sw-screen-share - Fired when the screen share button is clicked. Detail: `{ active: boolean }`\n * @fires sw-hangup - Fired when the hangup button is clicked.\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary accent color.\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover.\n * @cssprop [--sw-color-danger=#ef4444] - Color for destructive actions.\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover.\n * @cssprop [--sw-color-background=#1a1a1a] - Component background.\n * @cssprop [--sw-color-surface=#2a2a2a] - Button surface color.\n * @cssprop [--sw-color-surface-hover=#3a3a3a] - Button surface on hover.\n * @cssprop [--sw-color-text=#ffffff] - Text color.\n * @cssprop [--sw-color-text-muted=#a0a0a0] - Muted text color.\n * @cssprop [--sw-color-border=#404040] - Border color.\n * @cssprop [--sw-color-active=#ef4444] - Active/toggled button color.\n * @cssprop [--sw-border-radius=8px] - Border radius for buttons.\n * @cssprop [--sw-font-family] - Font family stack.\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit.\n * @cssprop [--sw-space-2=8px] - Small spacing.\n * @cssprop [--sw-space-3=12px] - Medium spacing.\n * @cssprop [--sw-space-4=16px] - Standard spacing.\n * @cssprop [--sw-space-6=24px] - Large spacing.\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription, of } from 'rxjs';\nimport { switchMap, filter } from 'rxjs/operators';\nimport type { Observable } from 'rxjs';\nimport { callContext } from '../context/index.js';\n\n/**\n * Screen share status type\n */\nexport type ScreenShareStatus = 'inactive' | 'pending' | 'active';\n\n/**\n * SelfParticipant interface for call controls\n */\nexport interface SelfParticipant {\n id: string;\n audioMuted$: Observable<boolean | undefined>;\n videoMuted$: Observable<boolean | undefined>;\n screenShareStatus$?: Observable<string>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n muteVideo: () => Promise<void>;\n unmuteVideo: () => Promise<void>;\n startScreenShare?: () => Promise<void>;\n stopScreenShare?: () => Promise<void>;\n}\n\n/**\n * Call interface for call controls\n */\nexport interface CallControlsCall {\n self?: SelfParticipant | null;\n self$?: Observable<SelfParticipant | null>;\n capabilities$?: Observable<string[]>;\n hangup: () => Promise<void>;\n}\n\n/**\n * Button type for controls\n */\ntype ControlButtonType = 'mute-audio' | 'mute-video' | 'screen-share' | 'hangup';\n\n@customElement('sw-call-controls')\nexport class CallControls extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-background: #1a1a1a;\n --sw-color-surface: #2a2a2a;\n --sw-color-surface-hover: #3a3a3a;\n --sw-color-text: #ffffff;\n --sw-color-text-muted: #a0a0a0;\n --sw-color-border: #404040;\n --sw-color-active: #ef4444;\n --sw-border-radius: 8px;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-space-6: 24px;\n\n display: block;\n font-family: var(--sw-font-family);\n color: var(--sw-color-text);\n }\n\n .container {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-3);\n padding: var(--sw-space-3);\n background: var(--sw-color-background);\n border-radius: var(--sw-border-radius);\n flex-wrap: wrap;\n }\n\n .container.vertical {\n flex-direction: column;\n }\n\n .control-button {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: 50%;\n color: var(--sw-color-text);\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n transform 0.1s ease;\n }\n\n .control-button:hover:not(:disabled) {\n background: var(--sw-color-surface-hover);\n border-color: var(--sw-color-text-muted);\n }\n\n .control-button:focus {\n outline: none;\n box-shadow:\n 0 0 0 2px var(--sw-color-background),\n 0 0 0 4px var(--sw-color-primary);\n }\n\n .control-button:focus-visible {\n outline: 2px solid var(--sw-color-primary);\n outline-offset: 2px;\n }\n\n .control-button:active:not(:disabled) {\n transform: scale(0.95);\n }\n\n .control-button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .control-button.active {\n background: var(--sw-color-active);\n border-color: var(--sw-color-active);\n color: white;\n }\n\n .control-button.active:hover:not(:disabled) {\n background: var(--sw-color-danger-hover);\n border-color: var(--sw-color-danger-hover);\n }\n\n .control-button.hangup {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: white;\n }\n\n .control-button.hangup:hover:not(:disabled) {\n background: var(--sw-color-danger-hover);\n border-color: var(--sw-color-danger-hover);\n }\n\n .control-button.screen-share.active {\n background: var(--sw-color-primary);\n border-color: var(--sw-color-primary);\n }\n\n .control-button.screen-share.active:hover:not(:disabled) {\n background: var(--sw-color-primary-hover);\n border-color: var(--sw-color-primary-hover);\n }\n\n .button-icon {\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n }\n\n .tooltip {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n padding: var(--sw-space-1) var(--sw-space-2);\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 0.2s ease,\n visibility 0.2s ease;\n pointer-events: none;\n z-index: 10;\n }\n\n .tooltip::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: var(--sw-color-border);\n }\n\n .control-button:hover .tooltip,\n .control-button:focus .tooltip {\n opacity: 1;\n visibility: visible;\n }\n\n /* Vertical orientation tooltip positioning */\n .container.vertical .tooltip {\n bottom: auto;\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n }\n\n .container.vertical .tooltip::after {\n top: 50%;\n left: auto;\n right: 100%;\n transform: translateY(-50%);\n border: 6px solid transparent;\n border-right-color: var(--sw-color-border);\n }\n\n /* Overflow menu for responsive collapse */\n .overflow-menu {\n position: relative;\n }\n\n .overflow-button {\n display: none;\n }\n\n .overflow-content {\n display: none;\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n padding: var(--sw-space-2);\n gap: var(--sw-space-2);\n flex-direction: column;\n z-index: 20;\n }\n\n .overflow-menu.open .overflow-content {\n display: flex;\n }\n\n /* Responsive collapse at small widths */\n @media (max-width: 320px) {\n .container:not(.vertical) > .control-button:not(.hangup):not(.overflow-button) {\n display: none;\n }\n\n .container:not(.vertical) .overflow-button {\n display: flex;\n }\n\n /* Ensure overflow content buttons remain visible when menu is open */\n .container:not(.vertical) .overflow-content .control-button {\n display: flex;\n }\n }\n\n /* Dark mode adjustments */\n @media (prefers-color-scheme: light) {\n :host {\n --sw-color-background: #ffffff;\n --sw-color-surface: #f5f5f5;\n --sw-color-surface-hover: #e5e5e5;\n --sw-color-text: #1a1a1a;\n --sw-color-text-muted: #666666;\n --sw-color-border: #d4d4d4;\n }\n }\n\n :host([data-theme='light']) {\n --sw-color-background: #ffffff;\n --sw-color-surface: #f5f5f5;\n --sw-color-surface-hover: #e5e5e5;\n --sw-color-text: #1a1a1a;\n --sw-color-text-muted: #666666;\n --sw-color-border: #d4d4d4;\n }\n `;\n\n /**\n * The call object - can be provided via property or context\n */\n @property({ type: Object }) call?: CallControlsCall;\n\n /**\n * Consume call from context if not provided as property\n */\n @consume({ context: callContext, subscribe: true })\n @state()\n private _contextCall?: CallControlsCall;\n\n /** Layout orientation: 'horizontal' or 'vertical'. */\n @property({ type: String }) orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n /** Whether to display tooltip labels on buttons. */\n @property({ type: Boolean, attribute: 'show-tooltips' }) showTooltips = true;\n\n /**\n * Current audio muted state\n */\n @state() private _audioMuted = false;\n\n /**\n * Current video muted state\n */\n @state() private _videoMuted = false;\n\n /**\n * Current screen share status\n */\n @state() private _screenShareStatus: ScreenShareStatus = 'inactive';\n\n /**\n * Available capabilities\n */\n @state() private _capabilities: string[] = [];\n\n /**\n * Is overflow menu open\n */\n @state() private _overflowOpen = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Cached self participant reference for button handlers.\n * Marked as @state() to trigger re-render when self becomes available,\n * enabling the control buttons.\n */\n @state() private selfParticipant?: SelfParticipant;\n\n /**\n * Get the effective call (property or context)\n */\n private get effectiveCall(): CallControlsCall | undefined {\n return this.call || this._contextCall;\n }\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('call') || changedProperties.has('_contextCall')) {\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n const call = this.effectiveCall;\n if (!call) return;\n\n // Use self$ observable if available (recommended for real calls)\n if (call.self$) {\n // Subscribe to self$ and switch to audioMuted$ when self becomes available\n const nonNullSelf$ = call.self$.pipe(\n filter((self): self is SelfParticipant => self !== null)\n );\n\n const audioSub = nonNullSelf$\n .pipe(\n switchMap((self) => {\n this.selfParticipant = self;\n return self.audioMuted$;\n })\n )\n .subscribe((muted) => {\n if (muted !== undefined) this._audioMuted = muted;\n });\n this.subscriptions.push(audioSub);\n\n // Subscribe to videoMuted$\n const videoSub = nonNullSelf$\n .pipe(switchMap((self) => self.videoMuted$))\n .subscribe((muted) => {\n if (muted !== undefined) this._videoMuted = muted;\n });\n this.subscriptions.push(videoSub);\n\n // Subscribe to screenShareStatus$\n const screenShareSub = nonNullSelf$\n .pipe(switchMap((self) => self.screenShareStatus$ ?? of('inactive' as ScreenShareStatus)))\n .subscribe((status) => {\n this._screenShareStatus = status as ScreenShareStatus;\n });\n this.subscriptions.push(screenShareSub);\n } else if (call.self) {\n // Fallback for synchronous self (tests, mocks)\n this.selfParticipant = call.self;\n\n if (call.self.audioMuted$) {\n this.subscriptions.push(\n call.self.audioMuted$.subscribe((muted) => {\n if (muted !== undefined) this._audioMuted = muted;\n })\n );\n }\n if (call.self.videoMuted$) {\n this.subscriptions.push(\n call.self.videoMuted$.subscribe((muted) => {\n if (muted !== undefined) this._videoMuted = muted;\n })\n );\n }\n if (call.self.screenShareStatus$) {\n this.subscriptions.push(\n call.self.screenShareStatus$.subscribe((status) => {\n this._screenShareStatus = status as ScreenShareStatus;\n })\n );\n }\n }\n\n // Subscribe to capabilities\n if (call.capabilities$) {\n this.subscriptions.push(\n call.capabilities$.subscribe((caps) => {\n this._capabilities = caps;\n })\n );\n }\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.selfParticipant = undefined;\n }\n\n /**\n * Check if capability is available\n */\n private hasCapability(capability: string): boolean {\n // If no capabilities defined, assume all available\n if (!this._capabilities || this._capabilities.length === 0) {\n return true;\n }\n return this._capabilities.includes(capability);\n }\n\n /**\n * Handle mute audio toggle\n */\n private async handleMuteAudio(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeMuted = !this._audioMuted;\n\n try {\n if (this._audioMuted) {\n await self.unmute();\n } else {\n await self.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-audio', {\n detail: { muted: willBeMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle audio mute:', error);\n }\n }\n\n /**\n * Handle mute video toggle\n */\n private async handleMuteVideo(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeMuted = !this._videoMuted;\n\n try {\n if (this._videoMuted) {\n await self.unmuteVideo();\n } else {\n await self.muteVideo();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-video', {\n detail: { muted: willBeMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle video mute:', error);\n }\n }\n\n /**\n * Handle screen share toggle\n */\n private async handleScreenShare(): Promise<void> {\n const self = this.selfParticipant ?? this.effectiveCall?.self;\n if (!self) return;\n\n // Capture the intended new state before calling the SDK\n const willBeActive = this._screenShareStatus !== 'active';\n\n try {\n if (this._screenShareStatus === 'active') {\n await self.stopScreenShare?.();\n } else {\n await self.startScreenShare?.();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-screen-share', {\n detail: { active: willBeActive },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle screen share:', error);\n }\n }\n\n /**\n * Handle hangup\n */\n private async handleHangup(): Promise<void> {\n const call = this.effectiveCall;\n if (!call) return;\n\n try {\n await call.hangup();\n\n this.dispatchEvent(\n new CustomEvent('sw-hangup', {\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to hangup:', error);\n }\n }\n\n /**\n * Toggle overflow menu\n */\n private toggleOverflow(): void {\n this._overflowOpen = !this._overflowOpen;\n }\n\n /**\n * Get tooltip text for button\n */\n private getTooltipText(type: ControlButtonType): string {\n switch (type) {\n case 'mute-audio':\n return this._audioMuted ? 'Unmute Audio' : 'Mute Audio';\n case 'mute-video':\n return this._videoMuted ? 'Turn On Camera' : 'Turn Off Camera';\n case 'screen-share':\n return this._screenShareStatus === 'active' ? 'Stop Sharing' : 'Share Screen';\n case 'hangup':\n return 'End Call';\n }\n }\n\n /**\n * Render button icon\n */\n private renderIcon(type: ControlButtonType) {\n switch (type) {\n case 'mute-audio':\n return this._audioMuted\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z\"\n />\n </svg>`;\n\n case 'mute-video':\n return this._videoMuted\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`;\n\n case 'screen-share':\n return this._screenShareStatus === 'active'\n ? html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M21.22 18.02l2 2H24v-2h-2.78zm.77-2l.01-10c0-1.11-.9-2-2-2H7.22l5.23 5.23c.18-.04.36-.07.55-.1V7.02l4 3.73-1.58 1.47 5.54 5.54c.61-.33 1.03-.99 1.03-1.74zM2.39 1.73L1.11 3l1.54 1.54c-.4.36-.65.89-.65 1.48v10c0 1.1.89 2 2 2H0v2h18.13l2.71 2.71 1.27-1.27L2.39 1.73zM7 15.02c.31-1.48.92-2.95 2.07-4.06l1.59 1.59c-1.54.38-2.7 1.18-3.66 2.47z\"\n />\n </svg>`\n : html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6zm9 9v-4h3l-4-4-4 4h3v4h2z\"\n />\n </svg>`;\n\n case 'hangup':\n return html`<svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z\"\n />\n </svg>`;\n }\n }\n\n /**\n * Render a control button\n */\n private renderButton(\n type: ControlButtonType,\n options: {\n onClick: () => void;\n active?: boolean;\n disabled?: boolean;\n className?: string;\n }\n ) {\n const { onClick, active = false, disabled = false, className = '' } = options;\n const tooltipText = this.getTooltipText(type);\n\n return html`\n <button\n class=\"control-button ${type} ${active ? 'active' : ''} ${className}\"\n part=\"button ${active ? 'button-active' : ''} ${disabled ? 'button-disabled' : ''}\"\n aria-label=\"${tooltipText}\"\n aria-pressed=\"${type !== 'hangup' ? active : nothing}\"\n ?disabled=${disabled}\n @click=${onClick}\n >\n ${this.renderIcon(type)}\n ${this.showTooltips\n ? html` <span class=\"tooltip\" part=\"tooltip\">${tooltipText}</span> `\n : nothing}\n </button>\n `;\n }\n\n /**\n * Render the component\n */\n render() {\n const call = this.effectiveCall;\n const hasSelf = Boolean(this.selfParticipant ?? call?.self);\n\n // Check capabilities - server sends: self, member, vmuted, layout, digit, screenshare, device, lock, end\n // Having 'self' capability implies self-controls are available (mute audio/video)\n const hasSelfCapability = this.hasCapability('self');\n const canMuteAudio =\n hasSelfCapability || this.hasCapability('selfMuteAudio') || this.hasCapability('muteAudio');\n const canMuteVideo =\n hasSelfCapability ||\n this.hasCapability('vmuted') ||\n this.hasCapability('selfMuteVideo') ||\n this.hasCapability('muteVideo');\n const canScreenShare =\n (this.hasCapability('screenshare') || this.hasCapability('screenShare')) &&\n Boolean(this.selfParticipant?.startScreenShare ?? call?.self?.startScreenShare);\n const canHangup = true; // Always allow hangup\n\n return html`\n <div\n class=\"container ${this.orientation === 'vertical' ? 'vertical' : ''}\"\n part=\"container\"\n role=\"toolbar\"\n aria-label=\"Call controls\"\n >\n ${this.renderButton('mute-audio', {\n onClick: () => this.handleMuteAudio(),\n active: this._audioMuted,\n disabled: !hasSelf || !canMuteAudio\n })}\n ${this.renderButton('mute-video', {\n onClick: () => this.handleMuteVideo(),\n active: this._videoMuted,\n disabled: !hasSelf || !canMuteVideo\n })}\n ${this.renderButton('screen-share', {\n onClick: () => this.handleScreenShare(),\n active: this._screenShareStatus === 'active',\n disabled: !hasSelf || !canScreenShare\n })}\n ${this.renderButton('hangup', {\n onClick: () => this.handleHangup(),\n disabled: !call || !canHangup,\n className: 'hangup'\n })}\n\n <!-- Overflow menu for responsive collapse -->\n <div class=\"overflow-menu ${this._overflowOpen ? 'open' : ''}\">\n <button\n class=\"control-button overflow-button\"\n aria-label=\"More options\"\n aria-expanded=\"${this._overflowOpen}\"\n @click=${this.toggleOverflow}\n >\n <svg class=\"button-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n />\n </svg>\n </button>\n <div class=\"overflow-content\">\n ${this.renderButton('mute-audio', {\n onClick: () => this.handleMuteAudio(),\n active: this._audioMuted,\n disabled: !hasSelf || !canMuteAudio\n })}\n ${this.renderButton('mute-video', {\n onClick: () => this.handleMuteVideo(),\n active: this._videoMuted,\n disabled: !hasSelf || !canMuteVideo\n })}\n ${this.renderButton('screen-share', {\n onClick: () => this.handleScreenShare(),\n active: this._screenShareStatus === 'active',\n disabled: !hasSelf || !canScreenShare\n })}\n </div>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-controls': CallControls;\n }\n}\n"],"names":["CallControls","LitElement","changedProperties","call","nonNullSelf$","filter","self","audioSub","switchMap","muted","videoSub","screenShareSub","of","status","caps","sub","capability","_a","willBeMuted","error","willBeActive","_b","_c","type","html","options","onClick","active","disabled","className","tooltipText","nothing","hasSelf","hasSelfCapability","canMuteAudio","canMuteVideo","canScreenShare","css","__decorateClass","property","consume","callContext","state","customElement"],"mappings":"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"}
|
|
@@ -51,7 +51,7 @@ export type ClickToCallStatus = 'idle' | 'connecting' | 'connected' | 'disconnec
|
|
|
51
51
|
* Self participant interface for click-to-call component
|
|
52
52
|
*/
|
|
53
53
|
export interface ClickToCallSelf {
|
|
54
|
-
audioMuted$: Observable<boolean>;
|
|
54
|
+
audioMuted$: Observable<boolean | undefined>;
|
|
55
55
|
mute(): Promise<void>;
|
|
56
56
|
unmute(): Promise<void>;
|
|
57
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"click-to-call.d.ts","sourceRoot":"","sources":["../../src/components/click-to-call.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,eAAe,GAAG,OAAO,CAAC;AAEhG;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"click-to-call.d.ts","sourceRoot":"","sources":["../../src/components/click-to-call.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,eAAe,GAAG,OAAO,CAAC;AAEhG;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CACF,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7C,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B;AAED,qBACa,oBAAqB,SAAQ,UAAU;IAClD,MAAM,CAAC,MAAM,0BA+MX;IAEF;;OAEG;IAEH,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAQ;IAExC;;OAEG;IAEH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IAEH,KAAK,EAAE,MAAM,CAAU;IAEvB;;OAEG;IAEH,SAAS,EAAE,OAAO,CAAQ;IAE1B;;OAEG;IAEH,OAAO,CAAC,MAAM,CAA6B;IAE3C;;OAEG;IAEH,OAAO,CAAC,IAAI,CAAgC;IAE5C;;OAEG;IAEH,OAAO,CAAC,UAAU,CAAkB;IAEpC;;OAEG;IACH,OAAO,CAAC,eAAe,CAAgC;IAEvD;;OAEG;IAEH,OAAO,CAAC,YAAY,CAAa;IAEjC;;OAEG;IAEH,OAAO,CAAC,YAAY,CAAc;IAElC;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAuB;IAE/C;;OAEG;IACH,OAAO,CAAC,aAAa,CAAuB;IAE5C,oBAAoB;YAKN,YAAY;IAkC1B,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;YAgBT,UAAU;YAsBV,MAAM;IAYpB,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,oBAAoB;IAiD5B,OAAO,CAAC,gBAAgB;IASxB,MAAM;CAeP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,oBAAoB,CAAC;KAC1C;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"click-to-call.js","sources":["../../src/components/click-to-call.ts"],"sourcesContent":["/**\n * Click to Call Component\n *\n * A single button widget to initiate calls to a preconfigured destination.\n * Shows minimal UI with call status, duration, and basic controls.\n *\n * @example\n * ```html\n * <sw-click-to-call\n * destination=\"sip:support@example.com\"\n * .client=${signalWire}\n * label=\"Call Support\"\n * ></sw-click-to-call>\n * ```\n *\n * @fires sw-dial - Fired when a call is initiated. Detail: `{ destination: string }`\n * @fires sw-hangup - Fired when the hangup button is clicked.\n * @fires sw-mute-toggle - Fired when the mute button is toggled. Detail: `{ muted: boolean }`\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary brand color\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover\n * @cssprop [--sw-color-success=#10b981] - Success/positive color\n * @cssprop [--sw-color-success-hover=#0ea472] - Success color on hover\n * @cssprop [--sw-color-danger=#ef4444] - Danger/destructive color\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover\n * @cssprop [--sw-color-warning=#f59e0b] - Warning color\n * @cssprop [--sw-color-text=#1f2937] - Primary text color\n * @cssprop [--sw-color-text-muted=#6b7280] - Secondary/muted text color\n * @cssprop [--sw-color-text-inverse=#ffffff] - Inverse text color for buttons\n * @cssprop [--sw-color-background=#ffffff] - Component background color\n * @cssprop [--sw-color-background-hover=#f3f4f6] - Background color on hover\n * @cssprop [--sw-color-border=#e5e7eb] - Border color\n * @cssprop [--sw-font-family=-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif] - Font family\n * @cssprop [--sw-font-size-sm=12px] - Small font size\n * @cssprop [--sw-font-size-base=14px] - Base font size\n * @cssprop [--sw-font-size-lg=16px] - Large font size\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit\n * @cssprop [--sw-space-2=8px] - Small spacing unit\n * @cssprop [--sw-space-3=12px] - Medium spacing unit\n * @cssprop [--sw-space-4=16px] - Large spacing unit\n * @cssprop [--sw-border-radius=8px] - Border radius for containers\n * @cssprop [--sw-border-radius-full=9999px] - Fully rounded border radius for circular buttons\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription, switchMap } from 'rxjs';\nimport type { Observable } from 'rxjs';\n\n/**\n * Call status type\n */\nexport type ClickToCallStatus = 'idle' | 'connecting' | 'connected' | 'disconnecting' | 'error';\n\n/**\n * Self participant interface for click-to-call component\n */\nexport interface ClickToCallSelf {\n audioMuted$: Observable<boolean>;\n mute(): Promise<void>;\n unmute(): Promise<void>;\n}\n\n/**\n * Call interface for click-to-call component\n */\nexport interface ClickToCallCall {\n status$: Observable<string>;\n hangup(): Promise<void>;\n self$?: Observable<ClickToCallSelf>;\n self?: ClickToCallSelf;\n}\n\n/**\n * Client interface for click-to-call component\n */\nexport interface ClickToCallClient {\n dial(\n destination: string,\n options?: { audio?: boolean; video?: boolean }\n ): Promise<ClickToCallCall>;\n}\n\n@customElement('sw-click-to-call')\nexport class ClickToCallComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-success: #10b981;\n --sw-color-success-hover: #0ea472;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-warning: #f59e0b;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-color-text-inverse: #ffffff;\n --sw-color-background: #ffffff;\n --sw-color-background-hover: #f3f4f6;\n --sw-color-border: #e5e7eb;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-sm: 12px;\n --sw-font-size-base: 14px;\n --sw-font-size-lg: 16px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-border-radius: 8px;\n --sw-border-radius-full: 9999px;\n\n display: inline-block;\n font-family: var(--sw-font-family);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-border: #374151;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-border: #374151;\n }\n }\n\n .container {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n }\n\n /* Idle state - call button */\n .call-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-3) var(--sw-space-4);\n min-width: 120px;\n min-height: 44px;\n background: var(--sw-color-success);\n color: var(--sw-color-text-inverse);\n border: none;\n border-radius: var(--sw-border-radius);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-lg);\n font-weight: 600;\n cursor: pointer;\n transition:\n background-color 0.15s ease,\n transform 0.1s ease;\n }\n\n .call-button:hover {\n background: var(--sw-color-success-hover);\n }\n\n .call-button:active {\n transform: scale(0.98);\n }\n\n .call-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .call-button svg {\n width: 20px;\n height: 20px;\n }\n\n /* Connected state - expanded controls */\n .active-call {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-3);\n padding: var(--sw-space-4);\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n min-width: 200px;\n }\n\n .status-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--sw-space-3);\n }\n\n .status {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: var(--sw-font-size-base);\n color: var(--sw-color-text);\n }\n\n .status-indicator {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--sw-color-success);\n }\n\n .status-indicator.connecting {\n background: var(--sw-color-warning);\n animation: pulse 1.5s ease-in-out infinite;\n }\n\n .status-indicator.error {\n background: var(--sw-color-danger);\n }\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n }\n\n .duration {\n font-variant-numeric: tabular-nums;\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n }\n\n .controls-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-2);\n }\n\n .control-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n background: var(--sw-color-background-hover);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius-full);\n cursor: pointer;\n color: var(--sw-color-text);\n transition: background-color 0.15s ease;\n }\n\n .control-button:hover {\n background: var(--sw-color-border);\n }\n\n .control-button.active {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .control-button svg {\n width: 20px;\n height: 20px;\n }\n\n .hangup-button {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .hangup-button:hover {\n background: var(--sw-color-danger-hover);\n }\n\n /* Error state */\n .error-message {\n color: var(--sw-color-danger);\n font-size: var(--sw-font-size-sm);\n text-align: center;\n }\n `;\n\n /**\n * Client for initiating calls (SignalWire)\n */\n @property({ attribute: false })\n client: ClickToCallClient | null = null;\n\n /**\n * Destination address to call\n */\n @property({ type: String })\n destination: string = '';\n\n /**\n * Button label when idle\n */\n @property({ type: String })\n label: string = 'Call';\n\n /**\n * Audio-only mode (no video)\n */\n @property({ type: Boolean, attribute: 'audio-only' })\n audioOnly: boolean = true;\n\n /**\n * Current call status\n */\n @state()\n private status: ClickToCallStatus = 'idle';\n\n /**\n * Current call object\n */\n @state()\n private call: ClickToCallCall | null = null;\n\n /**\n * Is audio muted\n */\n @state()\n private audioMuted: boolean = false;\n\n /**\n * Self participant reference for mute/unmute operations\n */\n private selfParticipant: ClickToCallSelf | null = null;\n\n /**\n * Call duration in seconds\n */\n @state()\n private callDuration: number = 0;\n\n /**\n * Error message\n */\n @state()\n private errorMessage: string = '';\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Duration timer interval\n */\n private durationInterval: number | null = null;\n\n /**\n * Call start time\n */\n private callStartTime: number | null = null;\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n private async initiateCall() {\n if (!this.client || !this.destination) {\n this.errorMessage = 'No client or destination configured';\n this.status = 'error';\n return;\n }\n\n try {\n this.status = 'connecting';\n this.errorMessage = '';\n\n // Dispatch dial event\n this.dispatchEvent(\n new CustomEvent('sw-dial', {\n detail: { destination: this.destination },\n bubbles: true,\n composed: true\n })\n );\n\n const call = await this.client.dial(this.destination, {\n audio: true,\n video: !this.audioOnly\n });\n\n this.call = call;\n this.subscribeToCall(call);\n } catch (error) {\n this.status = 'error';\n this.errorMessage = error instanceof Error ? error.message : 'Failed to connect';\n console.error('Click-to-call error:', error);\n }\n }\n\n private subscribeToCall(call: ClickToCallCall) {\n // Subscribe to status\n if (call.status$) {\n const statusSub = call.status$.subscribe((status) => {\n if (status === 'active' || status === 'connected') {\n this.status = 'connected';\n this.startDurationTimer();\n } else if (status === 'disconnecting') {\n this.status = 'disconnecting';\n } else if (status === 'disconnected' || status === 'destroyed' || status === 'failed') {\n this.handleCallEnded();\n }\n });\n this.subscriptions.push(statusSub);\n }\n\n // Subscribe to self$ observable to get audioMuted$ updates when self becomes available\n if (call.self$) {\n const selfSub = call.self$\n .pipe(\n switchMap((self) => {\n this.selfParticipant = self;\n return self.audioMuted$;\n })\n )\n .subscribe((muted) => {\n this.audioMuted = muted;\n });\n this.subscriptions.push(selfSub);\n }\n }\n\n private startDurationTimer() {\n this.callStartTime = Date.now();\n this.callDuration = 0;\n\n this.durationInterval = window.setInterval(() => {\n if (this.callStartTime) {\n this.callDuration = Math.floor((Date.now() - this.callStartTime) / 1000);\n }\n }, 1000);\n }\n\n private stopDurationTimer() {\n if (this.durationInterval) {\n clearInterval(this.durationInterval);\n this.durationInterval = null;\n }\n this.callStartTime = null;\n }\n\n private formatDuration(seconds: number): string {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n private handleCallEnded() {\n this.cleanup();\n this.status = 'idle';\n this.call = null;\n this.selfParticipant = null;\n this.audioMuted = false;\n this.callDuration = 0;\n\n this.dispatchEvent(\n new CustomEvent('sw-hangup', {\n bubbles: true,\n composed: true\n })\n );\n }\n\n private async toggleMute() {\n if (!this.selfParticipant) return;\n\n try {\n if (this.audioMuted) {\n await this.selfParticipant.unmute();\n } else {\n await this.selfParticipant.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-toggle', {\n detail: { muted: !this.audioMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle mute:', error);\n }\n }\n\n private async hangup() {\n if (!this.call) return;\n\n try {\n this.status = 'disconnecting';\n await this.call.hangup();\n } catch (error) {\n console.error('Failed to hangup:', error);\n this.handleCallEnded();\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.stopDurationTimer();\n }\n\n private renderIdleState() {\n return html`\n <button\n class=\"call-button\"\n part=\"button\"\n @click=${this.initiateCall}\n ?disabled=${!this.client || !this.destination}\n aria-label=\"${this.label}\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M6.62 10.79c1.44 2.83 3.76 5.15 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z\"\n />\n </svg>\n ${this.label}\n </button>\n `;\n }\n\n private renderConnectingState() {\n return html`\n <div class=\"active-call\" part=\"controls\">\n <div class=\"status-row\">\n <div class=\"status\" part=\"status\">\n <span class=\"status-indicator connecting\"></span>\n Connecting...\n </div>\n </div>\n </div>\n `;\n }\n\n private renderConnectedState() {\n return html`\n <div class=\"active-call\" part=\"controls\">\n <div class=\"status-row\">\n <div class=\"status\" part=\"status\">\n <span class=\"status-indicator\"></span>\n Connected\n </div>\n <span class=\"duration\" part=\"duration\">${this.formatDuration(this.callDuration)}</span>\n </div>\n <div class=\"controls-row\">\n <button\n class=\"control-button ${this.audioMuted ? 'active' : ''}\"\n @click=${this.toggleMute}\n aria-label=\"${this.audioMuted ? 'Unmute' : 'Mute'}\"\n aria-pressed=\"${this.audioMuted}\"\n >\n ${this.audioMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z\"\n />\n </svg>`}\n </button>\n <button class=\"control-button hangup-button\" @click=${this.hangup} aria-label=\"Hang up\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z\"\n />\n </svg>\n </button>\n </div>\n </div>\n `;\n }\n\n private renderErrorState() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.renderIdleState()}\n ${this.errorMessage ? html`<div class=\"error-message\">${this.errorMessage}</div>` : null}\n </div>\n `;\n }\n\n render() {\n if (this.status === 'error') {\n return this.renderErrorState();\n }\n\n if (this.status === 'connecting') {\n return html` <div class=\"container\" part=\"container\">${this.renderConnectingState()}</div> `;\n }\n\n if (this.status === 'connected' || this.status === 'disconnecting') {\n return html` <div class=\"container\" part=\"container\">${this.renderConnectedState()}</div> `;\n }\n\n return html` <div class=\"container\" part=\"container\">${this.renderIdleState()}</div> `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-click-to-call': ClickToCallComponent;\n }\n}\n"],"names":["ClickToCallComponent","LitElement","call","error","statusSub","status","selfSub","switchMap","self","muted","seconds","mins","secs","sub","html","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"click-to-call.js","sources":["../../src/components/click-to-call.ts"],"sourcesContent":["/**\n * Click to Call Component\n *\n * A single button widget to initiate calls to a preconfigured destination.\n * Shows minimal UI with call status, duration, and basic controls.\n *\n * @example\n * ```html\n * <sw-click-to-call\n * destination=\"sip:support@example.com\"\n * .client=${signalWire}\n * label=\"Call Support\"\n * ></sw-click-to-call>\n * ```\n *\n * @fires sw-dial - Fired when a call is initiated. Detail: `{ destination: string }`\n * @fires sw-hangup - Fired when the hangup button is clicked.\n * @fires sw-mute-toggle - Fired when the mute button is toggled. Detail: `{ muted: boolean }`\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary brand color\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover\n * @cssprop [--sw-color-success=#10b981] - Success/positive color\n * @cssprop [--sw-color-success-hover=#0ea472] - Success color on hover\n * @cssprop [--sw-color-danger=#ef4444] - Danger/destructive color\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover\n * @cssprop [--sw-color-warning=#f59e0b] - Warning color\n * @cssprop [--sw-color-text=#1f2937] - Primary text color\n * @cssprop [--sw-color-text-muted=#6b7280] - Secondary/muted text color\n * @cssprop [--sw-color-text-inverse=#ffffff] - Inverse text color for buttons\n * @cssprop [--sw-color-background=#ffffff] - Component background color\n * @cssprop [--sw-color-background-hover=#f3f4f6] - Background color on hover\n * @cssprop [--sw-color-border=#e5e7eb] - Border color\n * @cssprop [--sw-font-family=-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif] - Font family\n * @cssprop [--sw-font-size-sm=12px] - Small font size\n * @cssprop [--sw-font-size-base=14px] - Base font size\n * @cssprop [--sw-font-size-lg=16px] - Large font size\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit\n * @cssprop [--sw-space-2=8px] - Small spacing unit\n * @cssprop [--sw-space-3=12px] - Medium spacing unit\n * @cssprop [--sw-space-4=16px] - Large spacing unit\n * @cssprop [--sw-border-radius=8px] - Border radius for containers\n * @cssprop [--sw-border-radius-full=9999px] - Fully rounded border radius for circular buttons\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription, switchMap } from 'rxjs';\nimport type { Observable } from 'rxjs';\n\n/**\n * Call status type\n */\nexport type ClickToCallStatus = 'idle' | 'connecting' | 'connected' | 'disconnecting' | 'error';\n\n/**\n * Self participant interface for click-to-call component\n */\nexport interface ClickToCallSelf {\n audioMuted$: Observable<boolean | undefined>;\n mute(): Promise<void>;\n unmute(): Promise<void>;\n}\n\n/**\n * Call interface for click-to-call component\n */\nexport interface ClickToCallCall {\n status$: Observable<string>;\n hangup(): Promise<void>;\n self$?: Observable<ClickToCallSelf>;\n self?: ClickToCallSelf;\n}\n\n/**\n * Client interface for click-to-call component\n */\nexport interface ClickToCallClient {\n dial(\n destination: string,\n options?: { audio?: boolean; video?: boolean }\n ): Promise<ClickToCallCall>;\n}\n\n@customElement('sw-click-to-call')\nexport class ClickToCallComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-success: #10b981;\n --sw-color-success-hover: #0ea472;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-warning: #f59e0b;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-color-text-inverse: #ffffff;\n --sw-color-background: #ffffff;\n --sw-color-background-hover: #f3f4f6;\n --sw-color-border: #e5e7eb;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-sm: 12px;\n --sw-font-size-base: 14px;\n --sw-font-size-lg: 16px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-border-radius: 8px;\n --sw-border-radius-full: 9999px;\n\n display: inline-block;\n font-family: var(--sw-font-family);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-border: #374151;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-border: #374151;\n }\n }\n\n .container {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n }\n\n /* Idle state - call button */\n .call-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-3) var(--sw-space-4);\n min-width: 120px;\n min-height: 44px;\n background: var(--sw-color-success);\n color: var(--sw-color-text-inverse);\n border: none;\n border-radius: var(--sw-border-radius);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-lg);\n font-weight: 600;\n cursor: pointer;\n transition:\n background-color 0.15s ease,\n transform 0.1s ease;\n }\n\n .call-button:hover {\n background: var(--sw-color-success-hover);\n }\n\n .call-button:active {\n transform: scale(0.98);\n }\n\n .call-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .call-button svg {\n width: 20px;\n height: 20px;\n }\n\n /* Connected state - expanded controls */\n .active-call {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-3);\n padding: var(--sw-space-4);\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n min-width: 200px;\n }\n\n .status-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--sw-space-3);\n }\n\n .status {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: var(--sw-font-size-base);\n color: var(--sw-color-text);\n }\n\n .status-indicator {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--sw-color-success);\n }\n\n .status-indicator.connecting {\n background: var(--sw-color-warning);\n animation: pulse 1.5s ease-in-out infinite;\n }\n\n .status-indicator.error {\n background: var(--sw-color-danger);\n }\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n }\n\n .duration {\n font-variant-numeric: tabular-nums;\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n }\n\n .controls-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--sw-space-2);\n }\n\n .control-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n background: var(--sw-color-background-hover);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius-full);\n cursor: pointer;\n color: var(--sw-color-text);\n transition: background-color 0.15s ease;\n }\n\n .control-button:hover {\n background: var(--sw-color-border);\n }\n\n .control-button.active {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .control-button svg {\n width: 20px;\n height: 20px;\n }\n\n .hangup-button {\n background: var(--sw-color-danger);\n border-color: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .hangup-button:hover {\n background: var(--sw-color-danger-hover);\n }\n\n /* Error state */\n .error-message {\n color: var(--sw-color-danger);\n font-size: var(--sw-font-size-sm);\n text-align: center;\n }\n `;\n\n /**\n * Client for initiating calls (SignalWire)\n */\n @property({ attribute: false })\n client: ClickToCallClient | null = null;\n\n /**\n * Destination address to call\n */\n @property({ type: String })\n destination: string = '';\n\n /**\n * Button label when idle\n */\n @property({ type: String })\n label: string = 'Call';\n\n /**\n * Audio-only mode (no video)\n */\n @property({ type: Boolean, attribute: 'audio-only' })\n audioOnly: boolean = true;\n\n /**\n * Current call status\n */\n @state()\n private status: ClickToCallStatus = 'idle';\n\n /**\n * Current call object\n */\n @state()\n private call: ClickToCallCall | null = null;\n\n /**\n * Is audio muted\n */\n @state()\n private audioMuted: boolean = false;\n\n /**\n * Self participant reference for mute/unmute operations\n */\n private selfParticipant: ClickToCallSelf | null = null;\n\n /**\n * Call duration in seconds\n */\n @state()\n private callDuration: number = 0;\n\n /**\n * Error message\n */\n @state()\n private errorMessage: string = '';\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Duration timer interval\n */\n private durationInterval: number | null = null;\n\n /**\n * Call start time\n */\n private callStartTime: number | null = null;\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n private async initiateCall() {\n if (!this.client || !this.destination) {\n this.errorMessage = 'No client or destination configured';\n this.status = 'error';\n return;\n }\n\n try {\n this.status = 'connecting';\n this.errorMessage = '';\n\n // Dispatch dial event\n this.dispatchEvent(\n new CustomEvent('sw-dial', {\n detail: { destination: this.destination },\n bubbles: true,\n composed: true\n })\n );\n\n const call = await this.client.dial(this.destination, {\n audio: true,\n video: !this.audioOnly\n });\n\n this.call = call;\n this.subscribeToCall(call);\n } catch (error) {\n this.status = 'error';\n this.errorMessage = error instanceof Error ? error.message : 'Failed to connect';\n console.error('Click-to-call error:', error);\n }\n }\n\n private subscribeToCall(call: ClickToCallCall) {\n // Subscribe to status\n if (call.status$) {\n const statusSub = call.status$.subscribe((status) => {\n if (status === 'active' || status === 'connected') {\n this.status = 'connected';\n this.startDurationTimer();\n } else if (status === 'disconnecting') {\n this.status = 'disconnecting';\n } else if (status === 'disconnected' || status === 'destroyed' || status === 'failed') {\n this.handleCallEnded();\n }\n });\n this.subscriptions.push(statusSub);\n }\n\n // Subscribe to self$ observable to get audioMuted$ updates when self becomes available\n if (call.self$) {\n const selfSub = call.self$\n .pipe(\n switchMap((self) => {\n this.selfParticipant = self;\n return self.audioMuted$;\n })\n )\n .subscribe((muted) => {\n if (muted !== undefined) this.audioMuted = muted;\n });\n this.subscriptions.push(selfSub);\n }\n }\n\n private startDurationTimer() {\n this.callStartTime = Date.now();\n this.callDuration = 0;\n\n this.durationInterval = window.setInterval(() => {\n if (this.callStartTime) {\n this.callDuration = Math.floor((Date.now() - this.callStartTime) / 1000);\n }\n }, 1000);\n }\n\n private stopDurationTimer() {\n if (this.durationInterval) {\n clearInterval(this.durationInterval);\n this.durationInterval = null;\n }\n this.callStartTime = null;\n }\n\n private formatDuration(seconds: number): string {\n const mins = Math.floor(seconds / 60);\n const secs = seconds % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n }\n\n private handleCallEnded() {\n this.cleanup();\n this.status = 'idle';\n this.call = null;\n this.selfParticipant = null;\n this.audioMuted = false;\n this.callDuration = 0;\n\n this.dispatchEvent(\n new CustomEvent('sw-hangup', {\n bubbles: true,\n composed: true\n })\n );\n }\n\n private async toggleMute() {\n if (!this.selfParticipant) return;\n\n try {\n if (this.audioMuted) {\n await this.selfParticipant.unmute();\n } else {\n await this.selfParticipant.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-mute-toggle', {\n detail: { muted: !this.audioMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle mute:', error);\n }\n }\n\n private async hangup() {\n if (!this.call) return;\n\n try {\n this.status = 'disconnecting';\n await this.call.hangup();\n } catch (error) {\n console.error('Failed to hangup:', error);\n this.handleCallEnded();\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.stopDurationTimer();\n }\n\n private renderIdleState() {\n return html`\n <button\n class=\"call-button\"\n part=\"button\"\n @click=${this.initiateCall}\n ?disabled=${!this.client || !this.destination}\n aria-label=\"${this.label}\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M6.62 10.79c1.44 2.83 3.76 5.15 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z\"\n />\n </svg>\n ${this.label}\n </button>\n `;\n }\n\n private renderConnectingState() {\n return html`\n <div class=\"active-call\" part=\"controls\">\n <div class=\"status-row\">\n <div class=\"status\" part=\"status\">\n <span class=\"status-indicator connecting\"></span>\n Connecting...\n </div>\n </div>\n </div>\n `;\n }\n\n private renderConnectedState() {\n return html`\n <div class=\"active-call\" part=\"controls\">\n <div class=\"status-row\">\n <div class=\"status\" part=\"status\">\n <span class=\"status-indicator\"></span>\n Connected\n </div>\n <span class=\"duration\" part=\"duration\">${this.formatDuration(this.callDuration)}</span>\n </div>\n <div class=\"controls-row\">\n <button\n class=\"control-button ${this.audioMuted ? 'active' : ''}\"\n @click=${this.toggleMute}\n aria-label=\"${this.audioMuted ? 'Unmute' : 'Mute'}\"\n aria-pressed=\"${this.audioMuted}\"\n >\n ${this.audioMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z\"\n />\n </svg>`}\n </button>\n <button class=\"control-button hangup-button\" @click=${this.hangup} aria-label=\"Hang up\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z\"\n />\n </svg>\n </button>\n </div>\n </div>\n `;\n }\n\n private renderErrorState() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.renderIdleState()}\n ${this.errorMessage ? html`<div class=\"error-message\">${this.errorMessage}</div>` : null}\n </div>\n `;\n }\n\n render() {\n if (this.status === 'error') {\n return this.renderErrorState();\n }\n\n if (this.status === 'connecting') {\n return html` <div class=\"container\" part=\"container\">${this.renderConnectingState()}</div> `;\n }\n\n if (this.status === 'connected' || this.status === 'disconnecting') {\n return html` <div class=\"container\" part=\"container\">${this.renderConnectedState()}</div> `;\n }\n\n return html` <div class=\"container\" part=\"container\">${this.renderIdleState()}</div> `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-click-to-call': ClickToCallComponent;\n }\n}\n"],"names":["ClickToCallComponent","LitElement","call","error","statusSub","status","selfSub","switchMap","self","muted","seconds","mins","secs","sub","html","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
@@ -48,9 +48,9 @@ import type { Observable } from 'rxjs';
|
|
|
48
48
|
*/
|
|
49
49
|
export interface ControlParticipant {
|
|
50
50
|
id: string;
|
|
51
|
-
name$?: Observable<string>;
|
|
52
|
-
audioMuted$?: Observable<boolean>;
|
|
53
|
-
videoMuted$?: Observable<boolean>;
|
|
51
|
+
name$?: Observable<string | undefined>;
|
|
52
|
+
audioMuted$?: Observable<boolean | undefined>;
|
|
53
|
+
videoMuted$?: Observable<boolean | undefined>;
|
|
54
54
|
mute?(): Promise<void>;
|
|
55
55
|
unmute?(): Promise<void>;
|
|
56
56
|
muteVideo?(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"participant-controls.d.ts","sourceRoot":"","sources":["../../src/components/participant-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"participant-controls.d.ts","sourceRoot":"","sources":["../../src/components/participant-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,SAAS,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,qBACa,4BAA6B,SAAQ,UAAU;IAC1D,MAAM,CAAC,MAAM,0BAoMX;IAEF;;OAEG;IAEH,WAAW,EAAE,kBAAkB,GAAG,IAAI,CAAQ;IAE9C;;OAEG;IAEH,YAAY,EAAE,MAAM,EAAE,CAAM;IAE5B;;OAEG;IAEH,UAAU,EAAE,OAAO,CAAS;IAE5B;;OAEG;IAEH,OAAO,EAAE,OAAO,CAAS;IAEzB;;OAEG;IAEH,OAAO,CAAC,MAAM,CAAe;IAE7B;;OAEG;IAEH,OAAO,CAAC,eAAe,CAAyB;IAEhD;;OAEG;IAEH,OAAO,CAAC,UAAU,CAAkB;IAEpC;;OAEG;IAEH,OAAO,CAAC,UAAU,CAAkB;IAEpC;;OAEG;IAEH,OAAO,CAAC,QAAQ,CAAkB;IAElC;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C,iBAAiB;IAKjB,oBAAoB;IAKpB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAO/C,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,OAAO;IAKf,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,KAAK,SAAS,GAEpB;IAED,OAAO,KAAK,gBAAgB,GAI3B;YAEa,qBAAqB;YAsBrB,qBAAqB;YAsBrB,YAAY;IAkB1B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,eAAe;IAYvB,MAAM;CAqIP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,yBAAyB,EAAE,4BAA4B,CAAC;KACzD;CACF"}
|
|
@@ -22,19 +22,19 @@ let i = class extends d {
|
|
|
22
22
|
if (this.participant) {
|
|
23
23
|
if (this.participant.name$) {
|
|
24
24
|
const t = this.participant.name$.subscribe((e) => {
|
|
25
|
-
this.participantName = e;
|
|
25
|
+
e !== void 0 && (this.participantName = e);
|
|
26
26
|
});
|
|
27
27
|
this.subscriptions.push(t);
|
|
28
28
|
}
|
|
29
29
|
if (this.participant.audioMuted$) {
|
|
30
30
|
const t = this.participant.audioMuted$.subscribe((e) => {
|
|
31
|
-
this.audioMuted = e;
|
|
31
|
+
e !== void 0 && (this.audioMuted = e);
|
|
32
32
|
});
|
|
33
33
|
this.subscriptions.push(t);
|
|
34
34
|
}
|
|
35
35
|
if (this.participant.videoMuted$) {
|
|
36
36
|
const t = this.participant.videoMuted$.subscribe((e) => {
|
|
37
|
-
this.videoMuted = e;
|
|
37
|
+
e !== void 0 && (this.videoMuted = e);
|
|
38
38
|
});
|
|
39
39
|
this.subscriptions.push(t);
|
|
40
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"participant-controls.js","sources":["../../src/components/participant-controls.ts"],"sourcesContent":["/**\n * Participant Controls Component\n *\n * Individual participant control panel with actions like mute, remove,\n * volume control, and pin/spotlight.\n *\n * @example\n * ```html\n * <sw-participant-controls\n * .participant=${participant}\n * .capabilities=${['memberMuteAudio', 'memberRemove']}\n * ></sw-participant-controls>\n * ```\n *\n * @fires sw-participant-mute-audio - Fired to toggle a participant's audio. Detail: `{ participantId: string, muted: boolean }`\n * @fires sw-participant-mute-video - Fired to toggle a participant's video. Detail: `{ participantId: string, muted: boolean }`\n * @fires sw-participant-remove - Fired to remove a participant. Detail: `{ participantId: string }`\n * @fires sw-participant-volume - Fired when a participant's volume changes. Detail: `{ participantId: string, volume: number }`\n * @fires sw-participant-pin - Fired to pin/unpin a participant. Detail: `{ participantId: string, pinned: boolean }`\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary brand color\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover\n * @cssprop [--sw-color-success=#10b981] - Success/positive color\n * @cssprop [--sw-color-danger=#ef4444] - Danger/destructive color\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover\n * @cssprop [--sw-color-warning=#f59e0b] - Warning color\n * @cssprop [--sw-color-text=#1f2937] - Primary text color\n * @cssprop [--sw-color-text-muted=#6b7280] - Secondary/muted text color\n * @cssprop [--sw-color-text-inverse=#ffffff] - Inverse text color for active buttons\n * @cssprop [--sw-color-background=#ffffff] - Component background color\n * @cssprop [--sw-color-background-hover=#f3f4f6] - Background color on hover\n * @cssprop [--sw-color-background-active=#e5e7eb] - Background color on active/press\n * @cssprop [--sw-color-border=#e5e7eb] - Border color\n * @cssprop [--sw-font-family=-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif] - Font family\n * @cssprop [--sw-font-size-xs=11px] - Extra-small font size\n * @cssprop [--sw-font-size-sm=12px] - Small font size\n * @cssprop [--sw-font-size-base=14px] - Base font size\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit\n * @cssprop [--sw-space-2=8px] - Small spacing unit\n * @cssprop [--sw-space-3=12px] - Medium spacing unit\n * @cssprop [--sw-space-4=16px] - Large spacing unit\n * @cssprop [--sw-border-radius=8px] - Border radius for containers and buttons\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';\n\n/**\n * Participant interface for controls component\n */\nexport interface ControlParticipant {\n id: string;\n name$?: Observable<string>;\n audioMuted$?: Observable<boolean>;\n videoMuted$?: Observable<boolean>;\n mute?(): Promise<void>;\n unmute?(): Promise<void>;\n muteVideo?(): Promise<void>;\n unmuteVideo?(): Promise<void>;\n remove?(): Promise<void>;\n}\n\n@customElement('sw-participant-controls')\nexport class ParticipantControlsComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-success: #10b981;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-warning: #f59e0b;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-color-text-inverse: #ffffff;\n --sw-color-background: #ffffff;\n --sw-color-background-hover: #f3f4f6;\n --sw-color-background-active: #e5e7eb;\n --sw-color-border: #e5e7eb;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-xs: 11px;\n --sw-font-size-sm: 12px;\n --sw-font-size-base: 14px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-border-radius: 8px;\n\n display: block;\n font-family: var(--sw-font-family);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-background-active: #4b5563;\n --sw-color-border: #374151;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-background-active: #4b5563;\n --sw-color-border: #374151;\n }\n }\n\n .container {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n padding: var(--sw-space-3);\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n min-width: 200px;\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding-bottom: var(--sw-space-2);\n border-bottom: 1px solid var(--sw-color-border);\n }\n\n .participant-name {\n flex: 1;\n font-size: var(--sw-font-size-base);\n font-weight: 500;\n color: var(--sw-color-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .actions {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n }\n\n .action-button {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-2) var(--sw-space-3);\n background: var(--sw-color-background-hover);\n border: none;\n border-radius: var(--sw-border-radius);\n color: var(--sw-color-text);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n text-align: left;\n }\n\n .action-button:hover {\n background: var(--sw-color-background-active);\n }\n\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .action-button.active {\n background: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .action-button.active:hover {\n background: var(--sw-color-danger-hover);\n }\n\n .action-button.danger {\n color: var(--sw-color-danger);\n }\n\n .action-button.danger:hover {\n background: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .action-button svg {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n }\n\n .volume-control {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-1);\n }\n\n .volume-label {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: var(--sw-font-size-sm);\n color: var(--sw-color-text-muted);\n }\n\n .volume-label svg {\n width: 14px;\n height: 14px;\n }\n\n .volume-slider {\n width: 100%;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: var(--sw-color-border);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n }\n\n .volume-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n background: var(--sw-color-primary);\n border-radius: 50%;\n cursor: pointer;\n transition: transform 0.1s ease;\n }\n\n .volume-slider::-webkit-slider-thumb:hover {\n transform: scale(1.2);\n }\n\n .volume-slider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n background: var(--sw-color-primary);\n border-radius: 50%;\n border: none;\n cursor: pointer;\n }\n\n .no-actions {\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n text-align: center;\n padding: var(--sw-space-2);\n }\n `;\n\n /**\n * Participant object to control\n */\n @property({ attribute: false })\n participant: ControlParticipant | null = null;\n\n /**\n * Available capabilities (actions user can perform)\n */\n @property({ attribute: false })\n capabilities: string[] = [];\n\n /**\n * Whether to show volume slider\n */\n @property({ type: Boolean, attribute: 'show-volume' })\n showVolume: boolean = false;\n\n /**\n * Whether to show pin/spotlight button\n */\n @property({ type: Boolean, attribute: 'show-pin' })\n showPin: boolean = false;\n\n /**\n * Current volume (0-100)\n */\n @state()\n private volume: number = 100;\n\n /**\n * Participant name\n */\n @state()\n private participantName: string = 'Participant';\n\n /**\n * Audio mute state\n */\n @state()\n private audioMuted: boolean = false;\n\n /**\n * Video mute state\n */\n @state()\n private videoMuted: boolean = false;\n\n /**\n * Is pinned/spotlighted\n */\n @state()\n private isPinned: boolean = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToParticipant();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('participant')) {\n this.cleanup();\n this.subscribeToParticipant();\n }\n }\n\n private subscribeToParticipant() {\n if (!this.participant) return;\n\n if (this.participant.name$) {\n const nameSub = this.participant.name$.subscribe((name) => {\n this.participantName = name;\n });\n this.subscriptions.push(nameSub);\n }\n\n if (this.participant.audioMuted$) {\n const audioSub = this.participant.audioMuted$.subscribe((muted) => {\n this.audioMuted = muted;\n });\n this.subscriptions.push(audioSub);\n }\n\n if (this.participant.videoMuted$) {\n const videoSub = this.participant.videoMuted$.subscribe((muted) => {\n this.videoMuted = muted;\n });\n this.subscriptions.push(videoSub);\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\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 private get hasAnyCapability(): boolean {\n return (\n this.canMuteAudio || this.canMuteVideo || this.canRemove || this.showVolume || this.showPin\n );\n }\n\n private async handleToggleAudioMute() {\n if (!this.participant) return;\n\n try {\n if (this.audioMuted && this.participant.unmute) {\n await this.participant.unmute();\n } else if (!this.audioMuted && this.participant.mute) {\n await this.participant.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-mute-audio', {\n detail: { participant: this.participant, muted: !this.audioMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle audio mute:', error);\n }\n }\n\n private async handleToggleVideoMute() {\n if (!this.participant) return;\n\n try {\n if (this.videoMuted && this.participant.unmuteVideo) {\n await this.participant.unmuteVideo();\n } else if (!this.videoMuted && this.participant.muteVideo) {\n await this.participant.muteVideo();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-mute-video', {\n detail: { participant: this.participant, muted: !this.videoMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle video mute:', error);\n }\n }\n\n private async handleRemove() {\n if (!this.participant?.remove) return;\n\n try {\n await this.participant.remove();\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-remove', {\n detail: { participant: this.participant },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to remove participant:', error);\n }\n }\n\n private handleVolumeChange(e: Event) {\n const input = e.target as HTMLInputElement;\n this.volume = parseInt(input.value, 10);\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-volume', {\n detail: { participant: this.participant, volume: this.volume },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private handleTogglePin() {\n this.isPinned = !this.isPinned;\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-pin', {\n detail: { participant: this.participant, pinned: this.isPinned },\n bubbles: true,\n composed: true\n })\n );\n }\n\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n <div class=\"header\">\n <span class=\"participant-name\">${this.participantName}</span>\n </div>\n\n <div class=\"actions\">\n ${!this.hasAnyCapability\n ? html`<div class=\"no-actions\">No actions available</div>`\n : null}\n ${this.canMuteAudio\n ? html`\n <button\n class=\"action-button ${this.audioMuted ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleToggleAudioMute}\n aria-label=\"${this.audioMuted ? 'Unmute audio' : 'Mute audio'}\"\n >\n ${this.audioMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"\n />\n </svg>`}\n ${this.audioMuted ? 'Unmute' : 'Mute'}\n </button>\n `\n : null}\n ${this.canMuteVideo\n ? html`\n <button\n class=\"action-button ${this.videoMuted ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleToggleVideoMute}\n aria-label=\"${this.videoMuted ? 'Unmute video' : 'Mute video'}\"\n >\n ${this.videoMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`}\n ${this.videoMuted ? 'Enable video' : 'Disable video'}\n </button>\n `\n : null}\n ${this.showVolume\n ? html`\n <div class=\"volume-control\" part=\"slider\">\n <label class=\"volume-label\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"\n />\n </svg>\n Volume: ${this.volume}%\n </label>\n <input\n type=\"range\"\n class=\"volume-slider\"\n min=\"0\"\n max=\"100\"\n .value=${String(this.volume)}\n @input=${this.handleVolumeChange}\n aria-label=\"Participant volume\"\n />\n </div>\n `\n : null}\n ${this.showPin\n ? html`\n <button\n class=\"action-button ${this.isPinned ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleTogglePin}\n aria-label=\"${this.isPinned ? 'Unpin' : 'Pin'}\"\n aria-pressed=\"${this.isPinned}\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\" />\n </svg>\n ${this.isPinned ? 'Unpin' : 'Pin'}\n </button>\n `\n : null}\n ${this.canRemove\n ? html`\n <button\n class=\"action-button danger\"\n part=\"action-button\"\n @click=${this.handleRemove}\n aria-label=\"Remove participant\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n />\n </svg>\n Remove\n </button>\n `\n : null}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-participant-controls': ParticipantControlsComponent;\n }\n}\n"],"names":["ParticipantControlsComponent","LitElement","changedProperties","nameSub","name","audioSub","muted","videoSub","sub","error","_a","e","input","html","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"participant-controls.js","sources":["../../src/components/participant-controls.ts"],"sourcesContent":["/**\n * Participant Controls Component\n *\n * Individual participant control panel with actions like mute, remove,\n * volume control, and pin/spotlight.\n *\n * @example\n * ```html\n * <sw-participant-controls\n * .participant=${participant}\n * .capabilities=${['memberMuteAudio', 'memberRemove']}\n * ></sw-participant-controls>\n * ```\n *\n * @fires sw-participant-mute-audio - Fired to toggle a participant's audio. Detail: `{ participantId: string, muted: boolean }`\n * @fires sw-participant-mute-video - Fired to toggle a participant's video. Detail: `{ participantId: string, muted: boolean }`\n * @fires sw-participant-remove - Fired to remove a participant. Detail: `{ participantId: string }`\n * @fires sw-participant-volume - Fired when a participant's volume changes. Detail: `{ participantId: string, volume: number }`\n * @fires sw-participant-pin - Fired to pin/unpin a participant. Detail: `{ participantId: string, pinned: boolean }`\n *\n * @cssprop [--sw-color-primary=#044cf6] - Primary brand color\n * @cssprop [--sw-color-primary-hover=#0339c4] - Primary color on hover\n * @cssprop [--sw-color-success=#10b981] - Success/positive color\n * @cssprop [--sw-color-danger=#ef4444] - Danger/destructive color\n * @cssprop [--sw-color-danger-hover=#dc2626] - Danger color on hover\n * @cssprop [--sw-color-warning=#f59e0b] - Warning color\n * @cssprop [--sw-color-text=#1f2937] - Primary text color\n * @cssprop [--sw-color-text-muted=#6b7280] - Secondary/muted text color\n * @cssprop [--sw-color-text-inverse=#ffffff] - Inverse text color for active buttons\n * @cssprop [--sw-color-background=#ffffff] - Component background color\n * @cssprop [--sw-color-background-hover=#f3f4f6] - Background color on hover\n * @cssprop [--sw-color-background-active=#e5e7eb] - Background color on active/press\n * @cssprop [--sw-color-border=#e5e7eb] - Border color\n * @cssprop [--sw-font-family=-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif] - Font family\n * @cssprop [--sw-font-size-xs=11px] - Extra-small font size\n * @cssprop [--sw-font-size-sm=12px] - Small font size\n * @cssprop [--sw-font-size-base=14px] - Base font size\n * @cssprop [--sw-space-1=4px] - Smallest spacing unit\n * @cssprop [--sw-space-2=8px] - Small spacing unit\n * @cssprop [--sw-space-3=12px] - Medium spacing unit\n * @cssprop [--sw-space-4=16px] - Large spacing unit\n * @cssprop [--sw-border-radius=8px] - Border radius for containers and buttons\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';\n\n/**\n * Participant interface for controls component\n */\nexport interface ControlParticipant {\n id: string;\n name$?: Observable<string | undefined>;\n audioMuted$?: Observable<boolean | undefined>;\n videoMuted$?: Observable<boolean | undefined>;\n mute?(): Promise<void>;\n unmute?(): Promise<void>;\n muteVideo?(): Promise<void>;\n unmuteVideo?(): Promise<void>;\n remove?(): Promise<void>;\n}\n\n@customElement('sw-participant-controls')\nexport class ParticipantControlsComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-success: #10b981;\n --sw-color-danger: #ef4444;\n --sw-color-danger-hover: #dc2626;\n --sw-color-warning: #f59e0b;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-color-text-inverse: #ffffff;\n --sw-color-background: #ffffff;\n --sw-color-background-hover: #f3f4f6;\n --sw-color-background-active: #e5e7eb;\n --sw-color-border: #e5e7eb;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-xs: 11px;\n --sw-font-size-sm: 12px;\n --sw-font-size-base: 14px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-border-radius: 8px;\n\n display: block;\n font-family: var(--sw-font-family);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-background-active: #4b5563;\n --sw-color-border: #374151;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n --sw-color-background: #1f2937;\n --sw-color-background-hover: #374151;\n --sw-color-background-active: #4b5563;\n --sw-color-border: #374151;\n }\n }\n\n .container {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n padding: var(--sw-space-3);\n background: var(--sw-color-background);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-border-radius);\n min-width: 200px;\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding-bottom: var(--sw-space-2);\n border-bottom: 1px solid var(--sw-color-border);\n }\n\n .participant-name {\n flex: 1;\n font-size: var(--sw-font-size-base);\n font-weight: 500;\n color: var(--sw-color-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .actions {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-2);\n }\n\n .action-button {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-2) var(--sw-space-3);\n background: var(--sw-color-background-hover);\n border: none;\n border-radius: var(--sw-border-radius);\n color: var(--sw-color-text);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n text-align: left;\n }\n\n .action-button:hover {\n background: var(--sw-color-background-active);\n }\n\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .action-button.active {\n background: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .action-button.active:hover {\n background: var(--sw-color-danger-hover);\n }\n\n .action-button.danger {\n color: var(--sw-color-danger);\n }\n\n .action-button.danger:hover {\n background: var(--sw-color-danger);\n color: var(--sw-color-text-inverse);\n }\n\n .action-button svg {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n }\n\n .volume-control {\n display: flex;\n flex-direction: column;\n gap: var(--sw-space-1);\n }\n\n .volume-label {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: var(--sw-font-size-sm);\n color: var(--sw-color-text-muted);\n }\n\n .volume-label svg {\n width: 14px;\n height: 14px;\n }\n\n .volume-slider {\n width: 100%;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: var(--sw-color-border);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n }\n\n .volume-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n background: var(--sw-color-primary);\n border-radius: 50%;\n cursor: pointer;\n transition: transform 0.1s ease;\n }\n\n .volume-slider::-webkit-slider-thumb:hover {\n transform: scale(1.2);\n }\n\n .volume-slider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n background: var(--sw-color-primary);\n border-radius: 50%;\n border: none;\n cursor: pointer;\n }\n\n .no-actions {\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n text-align: center;\n padding: var(--sw-space-2);\n }\n `;\n\n /**\n * Participant object to control\n */\n @property({ attribute: false })\n participant: ControlParticipant | null = null;\n\n /**\n * Available capabilities (actions user can perform)\n */\n @property({ attribute: false })\n capabilities: string[] = [];\n\n /**\n * Whether to show volume slider\n */\n @property({ type: Boolean, attribute: 'show-volume' })\n showVolume: boolean = false;\n\n /**\n * Whether to show pin/spotlight button\n */\n @property({ type: Boolean, attribute: 'show-pin' })\n showPin: boolean = false;\n\n /**\n * Current volume (0-100)\n */\n @state()\n private volume: number = 100;\n\n /**\n * Participant name\n */\n @state()\n private participantName: string = 'Participant';\n\n /**\n * Audio mute state\n */\n @state()\n private audioMuted: boolean = false;\n\n /**\n * Video mute state\n */\n @state()\n private videoMuted: boolean = false;\n\n /**\n * Is pinned/spotlighted\n */\n @state()\n private isPinned: boolean = false;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToParticipant();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('participant')) {\n this.cleanup();\n this.subscribeToParticipant();\n }\n }\n\n private subscribeToParticipant() {\n if (!this.participant) return;\n\n if (this.participant.name$) {\n const nameSub = this.participant.name$.subscribe((name) => {\n if (name !== undefined) this.participantName = name;\n });\n this.subscriptions.push(nameSub);\n }\n\n if (this.participant.audioMuted$) {\n const audioSub = this.participant.audioMuted$.subscribe((muted) => {\n if (muted !== undefined) this.audioMuted = muted;\n });\n this.subscriptions.push(audioSub);\n }\n\n if (this.participant.videoMuted$) {\n const videoSub = this.participant.videoMuted$.subscribe((muted) => {\n if (muted !== undefined) this.videoMuted = muted;\n });\n this.subscriptions.push(videoSub);\n }\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\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 private get hasAnyCapability(): boolean {\n return (\n this.canMuteAudio || this.canMuteVideo || this.canRemove || this.showVolume || this.showPin\n );\n }\n\n private async handleToggleAudioMute() {\n if (!this.participant) return;\n\n try {\n if (this.audioMuted && this.participant.unmute) {\n await this.participant.unmute();\n } else if (!this.audioMuted && this.participant.mute) {\n await this.participant.mute();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-mute-audio', {\n detail: { participant: this.participant, muted: !this.audioMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle audio mute:', error);\n }\n }\n\n private async handleToggleVideoMute() {\n if (!this.participant) return;\n\n try {\n if (this.videoMuted && this.participant.unmuteVideo) {\n await this.participant.unmuteVideo();\n } else if (!this.videoMuted && this.participant.muteVideo) {\n await this.participant.muteVideo();\n }\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-mute-video', {\n detail: { participant: this.participant, muted: !this.videoMuted },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to toggle video mute:', error);\n }\n }\n\n private async handleRemove() {\n if (!this.participant?.remove) return;\n\n try {\n await this.participant.remove();\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-remove', {\n detail: { participant: this.participant },\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to remove participant:', error);\n }\n }\n\n private handleVolumeChange(e: Event) {\n const input = e.target as HTMLInputElement;\n this.volume = parseInt(input.value, 10);\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-volume', {\n detail: { participant: this.participant, volume: this.volume },\n bubbles: true,\n composed: true\n })\n );\n }\n\n private handleTogglePin() {\n this.isPinned = !this.isPinned;\n\n this.dispatchEvent(\n new CustomEvent('sw-participant-pin', {\n detail: { participant: this.participant, pinned: this.isPinned },\n bubbles: true,\n composed: true\n })\n );\n }\n\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n <div class=\"header\">\n <span class=\"participant-name\">${this.participantName}</span>\n </div>\n\n <div class=\"actions\">\n ${!this.hasAnyCapability\n ? html`<div class=\"no-actions\">No actions available</div>`\n : null}\n ${this.canMuteAudio\n ? html`\n <button\n class=\"action-button ${this.audioMuted ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleToggleAudioMute}\n aria-label=\"${this.audioMuted ? 'Unmute audio' : 'Mute audio'}\"\n >\n ${this.audioMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"\n />\n </svg>`}\n ${this.audioMuted ? 'Unmute' : 'Mute'}\n </button>\n `\n : null}\n ${this.canMuteVideo\n ? html`\n <button\n class=\"action-button ${this.videoMuted ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleToggleVideoMute}\n aria-label=\"${this.videoMuted ? 'Unmute video' : 'Mute video'}\"\n >\n ${this.videoMuted\n ? html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z\"\n />\n </svg>`\n : html`<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`}\n ${this.videoMuted ? 'Enable video' : 'Disable video'}\n </button>\n `\n : null}\n ${this.showVolume\n ? html`\n <div class=\"volume-control\" part=\"slider\">\n <label class=\"volume-label\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"\n />\n </svg>\n Volume: ${this.volume}%\n </label>\n <input\n type=\"range\"\n class=\"volume-slider\"\n min=\"0\"\n max=\"100\"\n .value=${String(this.volume)}\n @input=${this.handleVolumeChange}\n aria-label=\"Participant volume\"\n />\n </div>\n `\n : null}\n ${this.showPin\n ? html`\n <button\n class=\"action-button ${this.isPinned ? 'active' : ''}\"\n part=\"action-button\"\n @click=${this.handleTogglePin}\n aria-label=\"${this.isPinned ? 'Unpin' : 'Pin'}\"\n aria-pressed=\"${this.isPinned}\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\" />\n </svg>\n ${this.isPinned ? 'Unpin' : 'Pin'}\n </button>\n `\n : null}\n ${this.canRemove\n ? html`\n <button\n class=\"action-button danger\"\n part=\"action-button\"\n @click=${this.handleRemove}\n aria-label=\"Remove participant\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n />\n </svg>\n Remove\n </button>\n `\n : null}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-participant-controls': ParticipantControlsComponent;\n }\n}\n"],"names":["ParticipantControlsComponent","LitElement","changedProperties","nameSub","name","audioSub","muted","videoSub","sub","error","_a","e","input","html","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { DialpadComponent } from './components/dialpad.js';
|
|
|
14
14
|
export { ClickToCallComponent } from './components/click-to-call.js';
|
|
15
15
|
export { DirectoryComponent } from './components/directory.js';
|
|
16
16
|
export { ParticipantControlsComponent } from './components/participant-controls.js';
|
|
17
|
-
export type { Call,
|
|
17
|
+
export type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer, Participant } from './types/index.js';
|
|
18
18
|
export * from './context/index.js';
|
|
19
19
|
export { html, css, LitElement } from 'lit';
|
|
20
20
|
export type { TemplateResult, CSSResult } from 'lit';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AAGpF,YAAY,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AAGpF,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/H,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAQrD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAoB,CAAC;AAE3C;;GAEG;AACH,eAAO,MAAM,KAAK,EAAE,OAAc,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @signalwire/web-components\n * UI Components Library built with Lit\n */\n\n// Export components\nexport { ExampleButton } from './components/example-button.js';\nexport { CallMedia } from './components/call-media.js';\nexport { Participants } from './components/participants.js';\nexport { SelfMedia } from './components/self-media.js';\nexport { AudioLevel } from './components/audio-level.js';\nexport { DeviceSelector } from './components/device-selector.js';\nexport { CallControls } from './components/call-controls.js';\nexport { CallStatusComponent } from './components/call-status.js';\nexport { DialpadComponent } from './components/dialpad.js';\nexport { ClickToCallComponent } from './components/click-to-call.js';\nexport { DirectoryComponent } from './components/directory.js';\nexport { ParticipantControlsComponent } from './components/participant-controls.js';\n\n// Export types (re-exported from core)\nexport type { Call,
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @signalwire/web-components\n * UI Components Library built with Lit\n */\n\n// Export components\nexport { ExampleButton } from './components/example-button.js';\nexport { CallMedia } from './components/call-media.js';\nexport { Participants } from './components/participants.js';\nexport { SelfMedia } from './components/self-media.js';\nexport { AudioLevel } from './components/audio-level.js';\nexport { DeviceSelector } from './components/device-selector.js';\nexport { CallControls } from './components/call-controls.js';\nexport { CallStatusComponent } from './components/call-status.js';\nexport { DialpadComponent } from './components/dialpad.js';\nexport { ClickToCallComponent } from './components/click-to-call.js';\nexport { DirectoryComponent } from './components/directory.js';\nexport { ParticipantControlsComponent } from './components/participant-controls.js';\n\n// Export types (re-exported from core)\nexport type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer, Participant } from './types/index.js';\n\n// Export context\nexport * from './context/index.js';\n\n// Re-export Lit utilities for convenience\nexport { html, css, LitElement } from 'lit';\nexport type { TemplateResult, CSSResult } from 'lit';\n\n// ============================================================================\n// Library Ready Event (for async/dynamic script loading)\n// ============================================================================\n\ndeclare const __VERSION__: string;\n\n/**\n * Library version from package.json, injected at build time.\n */\nexport const version: string = __VERSION__;\n\n/**\n * Flag indicating the library has been loaded and is ready to use.\n */\nexport const ready: boolean = true;\n\n/**\n * Emits 'signalwire:web-components:ready' event when the library is loaded.\n *\n * Scripts that might load BEFORE the library (check flag first):\n * ```js\n * if (window.SignalWireUI?.ready) {\n * // Library already loaded, use it directly\n * initApp();\n * } else {\n * window.addEventListener('signalwire:web-components:ready', () => initApp());\n * }\n * ```\n */\nconst emitReadyEvent = (): void => {\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('signalwire:web-components:ready', {\n detail: { version: __VERSION__ }\n });\n window.dispatchEvent(event);\n }\n};\n\nemitReadyEvent();\n"],"names":["version","ready","emitReadyEvent","event"],"mappings":";;;;;;;;;;;;;;AAsCO,MAAMA,IAAkB,cAKlBC,IAAiB,IAexBC,IAAiB,MAAY;AACjC,MAAI,OAAO,SAAW,KAAa;AACjC,UAAMC,IAAQ,IAAI,YAAY,mCAAmC;AAAA,MAC/D,QAAQ,EAAE,SAAS,aAAA;AAAA,IAAY,CAChC;AACD,WAAO,cAAcA,CAAK;AAAA,EAC5B;AACF;AAEAD,EAAA;"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,44 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for call-media components.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Other types are defined locally for web-component specific needs.
|
|
4
|
+
* Types are imported directly from the SDK to stay in sync.
|
|
6
5
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
export type
|
|
6
|
+
import type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer } from '@signalwire/js';
|
|
7
|
+
export type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer };
|
|
8
|
+
export type Participant = CallParticipant;
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*/
|
|
14
|
-
export interface CallSelf {
|
|
15
|
-
id: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Helper to safely get self ID from a Call
|
|
10
|
+
* Helper to safely get self ID from a Call.
|
|
11
|
+
* Call.self is typed as CallSelfParticipant | null.
|
|
19
12
|
*/
|
|
20
13
|
export declare function getSelfId(call: Call | undefined): string | undefined;
|
|
21
14
|
/**
|
|
22
|
-
*
|
|
23
|
-
* The SDK
|
|
15
|
+
* Type-safe cast for participants array.
|
|
16
|
+
* The SDK's Call.participants is typed as CallParticipant[], so this
|
|
17
|
+
* provides a semantic helper for web-components that receive unknown[].
|
|
24
18
|
*/
|
|
25
19
|
export declare function castParticipants(participants: unknown[]): Participant[];
|
|
26
|
-
/**
|
|
27
|
-
* Participant interface for call participants
|
|
28
|
-
* Web-component specific with optional methods
|
|
29
|
-
*/
|
|
30
|
-
export interface Participant {
|
|
31
|
-
id: string;
|
|
32
|
-
name?: string;
|
|
33
|
-
name$?: Observable<string>;
|
|
34
|
-
audioMuted?: boolean;
|
|
35
|
-
audioMuted$?: Observable<boolean>;
|
|
36
|
-
videoMuted?: boolean;
|
|
37
|
-
videoMuted$?: Observable<boolean>;
|
|
38
|
-
mute?(): Promise<void>;
|
|
39
|
-
unmute?(): Promise<void>;
|
|
40
|
-
muteVideo?(): Promise<void>;
|
|
41
|
-
unmuteVideo?(): Promise<void>;
|
|
42
|
-
remove?(): Promise<void>;
|
|
43
|
-
}
|
|
44
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGhH,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC;AAG1F,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,CAEvE"}
|
package/dist/types/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
function r(t) {
|
|
2
|
-
const n = t == null ? void 0 : t.self;
|
|
3
|
-
return n == null ? void 0 : n.id;
|
|
4
|
-
}
|
|
5
1
|
function e(t) {
|
|
2
|
+
var n;
|
|
3
|
+
return (n = t == null ? void 0 : t.self) == null ? void 0 : n.id;
|
|
4
|
+
}
|
|
5
|
+
function r(t) {
|
|
6
6
|
return t;
|
|
7
7
|
}
|
|
8
8
|
export {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
r as castParticipants,
|
|
10
|
+
e as getSelfId
|
|
11
11
|
};
|
|
12
12
|
//# sourceMappingURL=index.js.map
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Type definitions for call-media components.\n *\n *
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Type definitions for call-media components.\n *\n * Types are imported directly from the SDK to stay in sync.\n */\n\nimport type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer } from '@signalwire/js';\n\n// Re-export SDK types for internal use\nexport type { Call, CallParticipant, CallSelfParticipant, DeviceController, LayoutLayer };\n\n// Use SDK's CallParticipant as the canonical Participant type\nexport type Participant = CallParticipant;\n\n/**\n * Helper to safely get self ID from a Call.\n * Call.self is typed as CallSelfParticipant | null.\n */\nexport function getSelfId(call: Call | undefined): string | undefined {\n return call?.self?.id;\n}\n\n/**\n * Type-safe cast for participants array.\n * The SDK's Call.participants is typed as CallParticipant[], so this\n * provides a semantic helper for web-components that receive unknown[].\n */\nexport function castParticipants(participants: unknown[]): Participant[] {\n return participants as Participant[];\n}\n"],"names":["getSelfId","call","_a","castParticipants","participants"],"mappings":"AAkBO,SAASA,EAAUC,GAA4C;AAA/D,MAAAC;AACL,UAAOA,IAAAD,KAAA,gBAAAA,EAAM,SAAN,gBAAAC,EAAY;AACrB;AAOO,SAASC,EAAiBC,GAAwC;AACvE,SAAOA;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalwire/web-components",
|
|
3
|
-
"version": "1.0.0-dev-
|
|
3
|
+
"version": "1.0.0-dev-20260416174405",
|
|
4
4
|
"description": "UI components library built with Lit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"rxjs": "^7.8.2"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
|
-
"@signalwire/js": "4.0.0-dev-
|
|
118
|
+
"@signalwire/js": "4.0.0-dev-20260416174405"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@playwright/test": "^1.56.1",
|