refactor component props with improved state & event handling

This commit is contained in:
Elijah Duffy
2026-04-07 15:34:22 -07:00
parent 4d9cd25b19
commit 8c57fd9d7b

View File

@@ -1,18 +1,22 @@
<!-- @component
The Jitsi component is a lightweight wrapper that manages loading the Jitsi Meet External API
and joining a Jitsi meeting room. It accepts configuration options and provides a callback
when the API is ready for use.
and joining a Jitsi meeting room. It accepts configuration options and several callbacks that
allow you to respond to basic meeting events such as joining and leaving. For advanced event
handling, use the getAPI() method to interact with the Jitsi instance directly.
WARNING: Assume that ALL properties are NOT reactive. Due to the nature of the Jitsi Meet
External API, changing properties after initialization will NOT update the Jitsi instance.
Instead, use the getAPI() method to interact with the Jitsi instance directly.
WARNING: While properties are reactive, in most cases changing them after the API has
initialized will trigger a complete re-render of the component, which will destroy the
existing Jitsi instance and interrupt any ongoing meetings. Use the getAPI() method to
interact with the Jitsi instance directly without causing a re-render.
-->
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
import type {
JitsiMeetExternalAPIOptions,
JitsiMeetExternalAPI
JitsiMeetExternalAPI,
VideoConferenceJoinedEvent,
VideoConferenceLeftEvent
} from '$lib/jitsi-iframe-api.d.ts';
import type { ClassValue } from 'svelte/elements';
@@ -32,29 +36,59 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
*/
class?: ClassValue;
/**
* Children are rendered inside the Jitsi container until the API is initialized.
* Optional snippet rendered until the API is initialized and the meeting
* is joined. Can be used to provide a loading indicator or placeholder content.
*/
children?: Snippet;
loading?: Snippet;
/** Whether to hold loading state until the meeting is joined (default: false). */
holdLoadingUntilJoined?: boolean;
/**
* Optional snippet rendered after the API is initialized and the meeting is joined.
* Can be used to provide additional UI elements or controls related to the meeting.
*/
joined?: Snippet<[api: JitsiMeetExternalAPI]>;
/**
* Optional snippet rendered after the user has left the meeting. Can be used to
* provide a farewell message or other post-meeting content.
*/
left?: Snippet;
/**
* Callback function that is called when the Jitsi API is loaded. Can
* be used to provide a loading indicator until the API is ready.
* @param api The Jitsi Meet External API instance.
*/
onready?: (api: JitsiMeetExternalAPI) => void;
onapiready?: (api: JitsiMeetExternalAPI) => void;
/** Callback triggered when the local user joins the meeting. */
onjoin?: (ev: VideoConferenceJoinedEvent) => void;
/** Callback triggered when the local user leaves the meeting. */
onleave?: (ev: VideoConferenceLeftEvent) => void;
}
let {
domain = 'https://meet.jit.si',
options,
class: classValue,
children,
onready
loading,
holdLoadingUntilJoined = false,
joined,
left,
onapiready,
onjoin,
onleave
}: Props = $props();
const src = $derived(`${new URL('external_api.js', domain).toString()}`);
let container: HTMLDivElement;
let ready = $state(false);
/** tracks whether the Jitsi API is ready */
let apiReady = $state(false);
/** tracks whether the local user has joined the meeting */
let joinedMeeting = $state(false);
/** tracks whether the local user has left the meeting */
let leftMeeting = $state(false);
/** whether to display the loading state */
const showLoading = $derived(holdLoadingUntilJoined ? !joinedMeeting : !apiReady);
let api = $state<JitsiMeetExternalAPI | null>(null);
onMount(() => {
@@ -64,13 +98,14 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
}
});
const onload = () => {
/** handles Jitsi script load event */
const handleScriptLoad = () => {
if (typeof window.JitsiMeetExternalAPI === 'undefined') {
console.error('Jitsi Meet External API is not available on the window object.');
return;
}
ready = true;
apiReady = true;
// Initialize the Jitsi Meet External API
api = new window.JitsiMeetExternalAPI(new URL(domain).host, {
...options,
@@ -80,8 +115,18 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
console.error('Failed to initialize Jitsi Meet External API.');
return;
}
// Call the onready callback if provided
onready?.(api);
// Call the onapiready callback if provided
onapiready?.(api);
// Attach event listeners for meeting join and leave events
api.addListener('videoConferenceJoined', (ev: VideoConferenceJoinedEvent) => {
joinedMeeting = true;
onjoin?.(ev);
});
api.addListener('videoConferenceLeft', (ev: VideoConferenceLeftEvent) => {
leftMeeting = true;
onleave?.(ev);
});
};
/**
@@ -94,11 +139,21 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
</script>
<svelte:head>
<script {src} {onload}></script>
<script {src} onload={handleScriptLoad}></script>
</svelte:head>
<div id="jitsi-container" bind:this={container} class={[classValue]}>
{#if !ready}
{@render children?.()}
<div class={['relative', classValue]}>
{#if showLoading}
<div class="absolute inset-0">{@render loading?.()}</div>
{/if}
{#if joinedMeeting && api && joined}
<div class="absolute inset-0 pointer-events-none">{@render joined(api)}</div>
{/if}
{#if leftMeeting}
<div class="absolute inset-0">{@render left?.()}</div>
{/if}
<div id="jitsi-container" bind:this={container} class={['w-full h-full min-w-0']}></div>
</div>