Popups

Intro

This is a pre-built utility implementation for checking whether the current running app is still up to date. This prevents cached single-page apps from getting updated too late and fetching files that might not exist anymore.

You can configure on which events the checker should run. An explicit button can call checkForUpdate(props, { force: true }) to reopen the prompt during a postponement. Both demo buttons use this option. Automatic checks continue to respect reminders.

/routes/+layout.svelte

<script lang="ts">
	import { dev, version } from '$app/environment';
	import { base } from '$app/paths';
	import { checkForUpdate, updateChecker, type UpdateCheckerProps } from '@leon8ix/jhana-ui/popup/update-checker';

	const props: UpdateCheckerProps = {
		// SvelteKit
		version,
		dev,
		base,
		updateUrl: () => `${location.pathname}${location.search}`,
		// Triggers/Events
		laterDelaySec: 60,
		onmount: true,
		onvisible: true,
		// Dict/Labels
		heading: 'Update Available',
		message: 'A new Version of Jhana UI is available. Do you want to update right now?',
		btnUpdateNow: 'Update now',
		btnAskLater: 'Ask later',
	};
</script>

<svelte:document {@attach updateChecker(props)} />

<!--
	The demo uses simulated versions. Real apps keep their SvelteKit version and dev values.
	An explicit user action can force a check while a reminder is pending.
-->
<button
	type="button"
	class="btn"
	onclick={() => checkForUpdate({ ...props, version: '2', dev: '1', laterDelaySec: 5 }, { force: true })}
>
	Show popup
</button>

Choose when to be reminded

Add laterOptions to turn Ask later into a split button. Its main action keeps the usual delay (60 seconds by default). The arrow or a right-click opens the app's reminder choices. Mount one CtxMenuRoot alongside PopupRoot in your app layout.

Open this example, choose “After the next file export”, then simulate a successful export. This demonstrates the completion hook for Infinipose's export workflow. In Cover Designer, the same hook can follow a successful cover save or export. The simulation does not write a file.

UpdateCheckerReminderDemo.svelte

<script lang="ts">
	import { checkForUpdate, type UpdateCheckerProps } from '@leon8ix/jhana-ui/popup/update-checker';

	let remindAfterExport = $state.raw<(() => Promise<void>) | undefined>();
	const props: UpdateCheckerProps = {
		version: 'demo-current',
		dev: 'demo-next', // Force an available update for this example.
		heading: 'Update available',
		message: 'A new version is available. Update now or choose when to be reminded.',
		btnUpdateNow: 'Update now',
		btnAskLater: 'Ask later',
		btnAskLaterOptions: 'Reminder options',
		laterOptions: [
			{ text: 'In five minutes', onselect: remind => void setTimeout(remind, 5 * 60 * 1000) },
			{ text: 'After the next file export', onselect: remind => (remindAfterExport = remind) },
		],
	};

	async function exportSucceeded() {
		const remind = remindAfterExport;
		remindAfterExport = undefined;
		await remind?.();
	}
</script>

<div class="row">
	<button
		type="button"
		class="btn"
		onclick={() => {
			remindAfterExport = undefined;
			return checkForUpdate(props, { force: true });
		}}>Show reminder options</button
	>
	<button type="button" class="btn" disabled={!remindAfterExport} onclick={exportSucceeded}>
		Simulate successful export
	</button>
</div>

Each option's onselect receives a one-use remind callback. Store it when the option is selected, then call it after the chosen operation succeeds. Keep it pending when an operation fails or is cancelled. Timed choices can use setTimeout with the same callback. Normal checks, including visibility changes, stay paused until it runs. The callback checks for an update again and asks for confirmation. It never installs the update automatically.

Choosing an option replaces the usual delay. Escape and the close button still use laterDelaySec. With no options, Ask later remains an ordinary button. Translate the choices and btnAskLaterOptions in the app. See docs/update-checker.md for integration details.

When a forced check opens the prompt, it cancels the previous delay and makes the old reminder callback inactive. The next choice sets a fresh reminder. A failed check or matching version preserves the existing reminder, and a reminder chosen while a check is in flight takes precedence.

Save state before updating

Set beforeUpdate in the checker configuration to save app state before reloading. It runs on Update now, including Enter activation. This callback uses Cover Designer's existing configuration state.

Before-update callback

import { app } from '$lib/app-state.svelte';
import type { UpdateCheckerProps } from '@leon8ix/jhana-ui/popup/update-checker';

const beforeUpdate: UpdateCheckerProps['beforeUpdate'] = () => {
	localStorage.setItem('cover-designer:update-draft', JSON.stringify(app.cfg));
};

// Include beforeUpdate in your update checker props.

Async callbacks are awaited with the button's loading indicator. The destination callback, updateUrl, runs afterward. A thrown error or rejected promise prevents navigation and leaves the dialog open for retry. Show error feedback in your callback before rethrowing. Closing the dialog during a pending save also prevents that update attempt from navigating. Your app owns restoring the saved state after startup.