File size: 3,073 Bytes
03a0d62 4331e77 03a0d62 89b5db9 660f054 0289d3a 4489403 21b8785 0289d3a 4331e77 21b8785 4331e77 660f054 21b8785 660f054 4331e77 660f054 976588c 4331e77 660f054 21b8785 4331e77 660f054 21b8785 4331e77 660f054 03a0d62 660f054 21b8785 a8bee07 0289d3a 660f054 4331e77 17d4d70 4331e77 17d4d70 4331e77 03a0d62 660f054 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { cubicOut } from "svelte/easing";
import { fade, fly } from "svelte/transition";
import Portal from "./Portal.svelte";
import { browser } from "$app/environment";
import CarbonClose from "~icons/carbon/close";
interface Props {
width?: string;
closeButton?: boolean;
disableFly?: boolean;
/** When false, clicking backdrop will not close the modal */
closeOnBackdrop?: boolean;
onclose?: () => void;
children?: import("svelte").Snippet;
}
let {
width = "max-w-sm",
children,
closeButton = false,
disableFly = false,
closeOnBackdrop = true,
onclose,
}: Props = $props();
let backdropEl: HTMLDivElement | undefined = $state();
let modalEl: HTMLDivElement | undefined = $state();
function handleKeydown(event: KeyboardEvent) {
// close on ESC
if (event.key === "Escape") {
event.preventDefault();
onclose?.();
}
}
function handleBackdropClick(event: MouseEvent) {
if (window?.getSelection()?.toString()) {
return;
}
if (event.target === backdropEl && closeOnBackdrop) {
onclose?.();
}
}
onMount(() => {
document.getElementById("app")?.setAttribute("inert", "true");
modalEl?.focus();
// Ensure Escape closes even if focus isn't within modal
window.addEventListener("keydown", handleKeydown, { capture: true });
});
onDestroy(() => {
if (!browser) return;
document.getElementById("app")?.removeAttribute("inert");
window.removeEventListener("keydown", handleKeydown, { capture: true });
});
</script>
<Portal>
<div
role="presentation"
tabindex="-1"
bind:this={backdropEl}
onclick={(e) => {
e.stopPropagation();
handleBackdropClick(e);
}}
transition:fade|local={{ easing: cubicOut, duration: 300 }}
class="fixed inset-0 z-40 flex items-center justify-center bg-black/80 backdrop-blur-sm dark:bg-black/50"
>
{#if disableFly}
<div
role="dialog"
tabindex="-1"
bind:this={modalEl}
onkeydown={handleKeydown}
class={[
"scrollbar-custom relative mx-auto max-h-[95dvh] max-w-[90dvw] overflow-y-auto overflow-x-hidden rounded-2xl bg-white shadow-2xl outline-none dark:bg-gray-800 dark:text-gray-200",
width,
]}
>
{#if closeButton}
<button class="absolute right-4 top-4 z-50" onclick={() => onclose?.()}>
<CarbonClose class="size-6 text-gray-700 dark:text-gray-300" />
</button>
{/if}
{@render children?.()}
</div>
{:else}
<div
role="dialog"
tabindex="-1"
bind:this={modalEl}
onkeydown={handleKeydown}
in:fly={{ y: 100 }}
class={[
"scrollbar-custom relative mx-auto max-h-[95dvh] max-w-[90dvw] overflow-y-auto overflow-x-hidden rounded-2xl bg-white shadow-2xl outline-none dark:bg-gray-800 dark:text-gray-200",
width,
]}
>
{#if closeButton}
<button class="absolute right-4 top-4 z-50" onclick={() => onclose?.()}>
<CarbonClose class="size-6 text-gray-700 dark:text-gray-300" />
</button>
{/if}
{@render children?.()}
</div>
{/if}
</div>
</Portal>
|