Compare commits

..

7 Commits

Author SHA1 Message Date
Elijah Duffy
b7c619c872 0.1.0 2026-04-07 17:47:43 -07:00
Elijah Duffy
408d3635f6 remove left snippet, leave this behaviour to consumer 2026-04-07 17:05:12 -07:00
Elijah Duffy
ff2dd60a95 remove tailwindcss classes in favour of raw styles 2026-04-07 16:52:39 -07:00
Elijah Duffy
bb07744081 update readme 2026-04-07 16:52:26 -07:00
Elijah Duffy
8c57fd9d7b refactor component props with improved state & event handling 2026-04-07 15:34:22 -07:00
Elijah Duffy
4d9cd25b19 add .nvmrc 2026-04-07 15:33:52 -07:00
Elijah Duffy
2cff9c79a7 add license to readme 2026-04-07 15:33:47 -07:00
4 changed files with 99 additions and 86 deletions

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24.12.0

View File

@@ -1,65 +1,3 @@
# Svelte library # Jitsi iFrame Svelte Integration
Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv). This package provides a component and typed API for the Jitsi Meet iFrame. If you prefer to build your own component, refer directly to the exported type definitions for the Jitsi iFrame API.
Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
pnpm dlx sv create --template library --types ts --add prettier eslint devtools-json --install pnpm jitsi
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
## Building
To build your library:
```sh
npm pack
```
To create a production version of your showcase app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
## Publishing
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
To publish your library to [npm](https://www.npmjs.com):
```sh
npm publish
```

View File

@@ -1,6 +1,7 @@
{ {
"name": "@svelte-toolkit/jitsi", "name": "@svelte-toolkit/jitsi",
"version": "0.0.4", "version": "0.1.0",
"license": "BSD-3-Clause",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git" "url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"

View File

@@ -1,18 +1,22 @@
<!-- @component <!-- @component
The Jitsi component is a lightweight wrapper that manages loading the Jitsi Meet External API 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 and joining a Jitsi meeting room. It accepts configuration options and several callbacks that
when the API is ready for use. 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 WARNING: While properties are reactive, in most cases changing them after the API has
External API, changing properties after initialization will NOT update the Jitsi instance. initialized will trigger a complete re-render of the component, which will destroy the
Instead, use the getAPI() method to interact with the Jitsi instance directly. 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"> <script lang="ts">
import { onMount, type Snippet } from 'svelte'; import { onMount, type Snippet } from 'svelte';
import type { import type {
JitsiMeetExternalAPIOptions, JitsiMeetExternalAPIOptions,
JitsiMeetExternalAPI JitsiMeetExternalAPI,
VideoConferenceJoinedEvent,
VideoConferenceLeftEvent
} from '$lib/jitsi-iframe-api.d.ts'; } from '$lib/jitsi-iframe-api.d.ts';
import type { ClassValue } from 'svelte/elements'; import type { ClassValue } from 'svelte/elements';
@@ -32,45 +36,72 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
*/ */
class?: ClassValue; 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.
* Overlay has pointer-events disabled to allow interaction with the underlying Jitsi
* interface.
*/
joinedOverlay?: Snippet<[api: JitsiMeetExternalAPI]>;
/** /**
* Callback function that is called when the Jitsi API is loaded. Can * Callback function that is called when the Jitsi API is loaded. Can
* be used to provide a loading indicator until the API is ready. * be used to provide a loading indicator until the API is ready.
* @param api The Jitsi Meet External API instance. * @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 { let {
domain = 'https://meet.jit.si', domain = 'https://meet.jit.si',
options, options,
class: classValue, class: classValue,
children, loading,
onready holdLoadingUntilJoined = false,
joinedOverlay,
onapiready,
onjoin,
onleave
}: Props = $props(); }: Props = $props();
const src = $derived(`${new URL('external_api.js', domain).toString()}`); const src = $derived(`${new URL('external_api.js', domain).toString()}`);
let container: HTMLDivElement; 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); let api = $state<JitsiMeetExternalAPI | null>(null);
onMount(() => { onMount(() => {
// check if we have a duplicate container // check if we have a duplicate container
if (document.querySelectorAll('#jitsi-container').length > 1) { if (document.querySelectorAll('#jitsi-iframe').length > 1) {
console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.'); console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.');
} }
}); });
const onload = () => { /** handles Jitsi script load event */
const handleScriptLoad = () => {
if (typeof window.JitsiMeetExternalAPI === 'undefined') { if (typeof window.JitsiMeetExternalAPI === 'undefined') {
console.error('Jitsi Meet External API is not available on the window object.'); console.error('Jitsi Meet External API is not available on the window object.');
return; return;
} }
ready = true; apiReady = true;
// Initialize the Jitsi Meet External API // Initialize the Jitsi Meet External API
api = new window.JitsiMeetExternalAPI(new URL(domain).host, { api = new window.JitsiMeetExternalAPI(new URL(domain).host, {
...options, ...options,
@@ -80,8 +111,18 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
console.error('Failed to initialize Jitsi Meet External API.'); console.error('Failed to initialize Jitsi Meet External API.');
return; return;
} }
// Call the onready callback if provided // Call the onapiready callback if provided
onready?.(api); 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 +135,43 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
</script> </script>
<svelte:head> <svelte:head>
<script {src} {onload}></script> <script {src} onload={handleScriptLoad}></script>
</svelte:head> </svelte:head>
<div id="jitsi-container" bind:this={container} class={[classValue]}> <div class={['jitsi-container', classValue]}>
{#if !ready} {#if showLoading}
{@render children?.()} <div class="overlay-container">{@render loading?.()}</div>
{/if} {/if}
{#if joinedMeeting && api && joinedOverlay}
<div class="overlay-container passthrough">{@render joinedOverlay(api)}</div>
{/if}
<div
id="jitsi-iframe"
bind:this={container}
class="fill-container"
style={leftMeeting ? 'display: none;' : undefined}
></div>
</div> </div>
<style lang="css">
.jitsi-container {
position: relative;
}
.fill-container {
width: 100%;
height: 100%;
min-width: 0px;
}
.overlay-container {
position: absolute;
inset: 0;
}
.passthrough {
pointer-events: none;
}
</style>