atagen

malice

public
Star0
Watch0
8c2389a6521c4b47b2140ecb55a82ad8b9c8ab67
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/ImageLightbox.svelteSvelte83 lines2.4 KB
1
<script lang="ts">
2
  import { tick } from 'svelte';
3
  import { lightbox } from './lightbox';
4
5
  let closeBtn: HTMLButtonElement | null = null;
6
  let returnFocus: HTMLElement | null = null;
7
8
  $: if ($lightbox) void focusClose();
9
10
  async function focusClose(): Promise<void> {
11
    if (!returnFocus) returnFocus = document.activeElement as HTMLElement | null;
12
    await tick();
13
    closeBtn?.focus();
14
  }
15
16
  function close(): void {
17
    lightbox.set(null);
18
    returnFocus?.focus();
19
    returnFocus = null;
20
  }
21
22
  function onKey(ev: KeyboardEvent): void {
23
    if ($lightbox && ev.key === 'Escape') {
24
      ev.stopPropagation();
25
      close();
26
    }
27
  }
28
</script>
29
30
<svelte:window on:keydown|capture={onKey} />
31
32
{#if $lightbox}
33
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
34
  <div class="lightbox" role="dialog" tabindex="-1" aria-modal="true" aria-label={$lightbox.filename} on:click|self={close}>
35
    <div class="lightbox-bar">
36
      <span class="lightbox-name">{$lightbox.filename}</span>
37
      <a class="ghost-btn" href={$lightbox.src} target="_blank" rel="noopener noreferrer">Open original</a>
38
      <a class="ghost-btn" href={$lightbox.src} download={$lightbox.filename}>Download</a>
39
      <button type="button" class="icon-btn" aria-label="Close image" title="Close (Esc)" bind:this={closeBtn} on:click={close}>
40
        <span aria-hidden="true">✕</span>
41
      </button>
42
    </div>
43
    <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
44
    <img class="lightbox-image" src={$lightbox.src} alt={$lightbox.alt} on:click={close} />
45
  </div>
46
{/if}
47
48
<style>
49
  .lightbox {
50
    position: fixed;
51
    inset: 0;
52
    z-index: 100;
53
    display: flex;
54
    flex-direction: column;
55
    align-items: center;
56
    justify-content: center;
57
    gap: 12px;
58
    padding: 56px 24px 24px;
59
    background: rgb(0 0 0 / 0.82);
60
    backdrop-filter: blur(4px);
61
  }
62
  .lightbox-bar {
63
    position: absolute;
64
    top: 0;
65
    left: 0;
66
    right: 0;
67
    display: flex;
68
    align-items: center;
69
    gap: 8px;
70
    padding: 10px 16px;
71
    color: #fff;
72
  }
73
  .lightbox-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
74
  .lightbox-bar a { text-decoration: none; }
75
  .lightbox-image {
76
    max-width: 100%;
77
    max-height: 100%;
78
    object-fit: contain;
79
    border-radius: 6px;
80
    box-shadow: 0 12px 40px rgb(0 0 0 / 0.5);
81
    cursor: zoom-out;
82
  }
83
</style>