# 灯箱

## 使用方法

`Lightbox` 用原生 `<dialog>` 打开媒体。`isOpen` 用 `v-model:is-open` 绑定。`media` 传一个对象就是单张图片或一段视频。传数组就是图库：上一张、下一张和计数一直留在页面上，到头时禁用，而不是消失。

`useLightbox({ media })` 管打开状态和下标。把 `bindings` 绑到 `Lightbox` 上，缩略图里调用 `open(index)`。

`hasZoom` 把图片变成按钮。双击、Enter、Space、`+`、`-` 在 1 倍和 2 倍之间切换。放大后可以拖动，方向键每次平移 50px。拖动结束时的那一下点击不会把对话框关掉。视频用浏览器自己的控件，不能缩放。`hasAutoPlay` 在显示出视频时开始播放。

Escape 和关闭按钮都会发出 `update:isOpen`，值为 `false`。点对话框或画面外的遮罩也一样。

```vue
<script setup lang="ts">
import { Lightbox, useLightbox } from '@astryx-vue/core/Lightbox'
import { Thumbnail } from '@astryx-vue/core/Thumbnail'

const photo = {
  src: '/template-assets/light-scene-horizontal-1.png',
  alt: 'Coastal shoreline with ocean waves',
  caption: 'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
}
const { bindings, open } = useLightbox({ media: photo })
</script>

<template>
  <Thumbnail :src="photo.src" :alt="photo.alt" :label="photo.alt" @click="open()" />
  <Lightbox v-bind="bindings" />
</template>
```

## 建议

| 建议 | 做法 |
| --- | --- |
| 推荐 | `alt` 写出这是什么，`caption` 补上画面里看不出来的信息。 |
| 推荐 | 图库从读者点的那张打开，调用 `open(index)`。 |
| 推荐 | 只有静止图片、而且细节值得看时才打开 `hasZoom`。 |
| 避免 | 给视频加 `hasZoom`。视频保持浏览器控件，缩放不会生效。 |
| 避免 | `alt` 留空。对话框名称和图片名称都从这里来。 |

### 结构

- **查看器遮罩**: 铺满视口、装着当前媒体和控件的对话框遮罩。
- **媒体**: 查看器里正在展示的图片或视频。
- **关闭按钮**: 关闭查看器的按钮。
- **上一张**: 画廊里切到上一项媒体的按钮。
- **下一张**: 画廊里切到下一项媒体的按钮。
- **说明文字**: 显示在当前媒体下方、由使用方提供的说明。
- **计数**: 画廊模式里显示的当前位置和总数。

## 示例

### 图库

缩略图网格打开全屏图库。点任意一张，灯箱就从那一张开始。上一张和下一张可以不关闭就翻完。

```vue
<script setup lang="ts">
import { Grid } from '@astryx-vue/core/Grid'
import { Lightbox, useLightbox } from '@astryx-vue/core/Lightbox'
import { Thumbnail } from '@astryx-vue/core/Thumbnail'

const photos = [
  {
    src: '/template-assets/Neutral-Backpack.png',
    alt: 'Backpack',
    caption: 'A backpack displayed on a neutral background.',
  },
  {
    src: '/template-assets/building.png',
    alt: 'Modern building',
    caption: 'A modern building with a contemporary architectural design.',
  },
  {
    src: '/template-assets/light-scene-horizontal-1.png',
    alt: 'Coastal shoreline with ocean waves',
    caption: 'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
  },
  {
    src: '/template-assets/illustrative-vertical-1.png',
    alt: 'Illustrated lakeside landscape at sunset',
    caption: 'A stylized landscape illustration featuring pink clouds reflected over a calm lake at sunset.',
  },
]

const { bindings, open } = useLightbox({ media: photos })
</script>

<template>
  <Grid :columns="2" :gap="2" :width="136">
    <Thumbnail
      v-for="(photo, index) in photos"
      :key="photo.src"
      :src="photo.src"
      :alt="photo.alt"
      :label="photo.alt"
      @click="open(index)"
    />
  </Grid>
  <Lightbox v-bind="bindings" />
</template>
```

### 视频

在灯箱里打开视频，使用浏览器自带控件。视频不能缩放，也不能平移。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Lightbox } from '@astryx-vue/core/Lightbox'
import { ref } from 'vue'

const isOpen = ref(false)
</script>

<template>
  <Button label="Play video" @click="isOpen = true" />
  <Lightbox
    :is-open="isOpen"
    :media="{
      src: '/template-assets/Nature-1.mp4',
      alt: 'Flower blooming in time-lapse',
      type: 'video',
      caption: 'A flower blooming in time-lapse',
    }"
    @update:is-open="isOpen = $event"
  />
</template>
```

### 缩放

打开缩放和平移。双击图片放大，拖动查看。再双击一次，或点关闭，回到原大小并退出。

```vue
<script setup lang="ts">
import { Lightbox } from '@astryx-vue/core/Lightbox'
import { Thumbnail } from '@astryx-vue/core/Thumbnail'
import { ref } from 'vue'

const isOpen = ref(false)
</script>

<template>
  <Thumbnail
    src="/template-assets/light-scene-horizontal-1.png"
    alt="Coastal shoreline with ocean waves"
    label="Coastal shoreline with ocean waves"
    @click="isOpen = true"
  />
  <Lightbox
    :is-open="isOpen"
    has-zoom
    :media="{
      src: '/template-assets/light-scene-horizontal-1.png',
      alt: 'Coastal shoreline with ocean waves',
      caption: 'A scenic coastline. Double-click to zoom in and drag to pan.',
    }"
    @update:is-open="isOpen = $event"
  />
</template>
```

## 属性

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `isOpen` | `boolean` | — | 对话框是否打开。用 `v-model:is-open` 绑定。 |
| `media` | `LightboxMedia \| readonly LightboxMedia[]` | — | 一项，或一组。数组会显示上一张、下一张和计数。 |
| `index` | `number` | — | 受控的图库下标。用 `v-model:index` 绑定。 |
| `defaultIndex` | `number` | `0` | 没有传 `index` 时的起始下标。 |
| `hasZoom` | `boolean` | `false` | 双击、Enter、Space、`+`、`-` 缩放图片。放大后方向键平移。 |
| `hasAutoPlay` | `boolean` | `false` | 显示出视频时开始播放。 |

`LightboxMedia` 是 `{ src, alt, caption?, type? }`。`type` 为 `image` 或 `video`，默认 `image`。

## 事件

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `update:isOpen` | `boolean` | 关闭、Escape 或点遮罩时，请求改变打开状态。 |
| `update:index` | `number` | 上一张、下一张，或未放大时按方向键，请求换到新的下标。 |

## useLightbox

```ts
const { bindings, open, close, isOpen, index, getTriggerProps } = useLightbox({
  media,
  hasZoom: false,
  hasAutoPlay: false,
})
```

`bindings` 是给 `v-bind` 用的属性和更新监听。`open(index)` 设置下标并打开对话框。`getTriggerProps(index)` 返回 `role="button"`、`tabindex="0"`、`aria-haspopup="dialog"`，以及键盘激活，给自定义触发元素用。

## 主题

对话框类名是 `astryx-lightbox`。说明文字用 large 字号、`on-dark` 颜色，最大宽度 600px，块方向起始边距为间距 2，行内两侧为间距 3。关闭和翻页按钮距画面边缘为间距 3。
