Control a lightbox programmatically
Open, close, and navigate a lightbox from application code.
Every provider exposes one controller contract:
interface LightboxController {
readonly photos: ComputedRef<readonly PhotoItem[]>
readonly count: ComputedRef<number>
readonly activeIndex: ComputedRef<number>
readonly activePhoto: ComputedRef<PhotoItem | null>
readonly isOpen: ComputedRef<boolean>
open(index?: number): Promise<void>
openById(id: string): Promise<void>
close(): Promise<void>
next(): void
prev(): void
toggleZoom(): void
}State is readonly. Invalid indexes and IDs reject with RangeError. Open and
close promises settle after the latest lifecycle intent is reconciled.
PhotoGroup template ref
<script setup lang="ts">
const group = ref<{
open(index?: number): Promise<void>
openById(id: string): Promise<void>
close(): Promise<void>
}>()
</script>
<template>
<PhotoGroup ref="group" :photos="photos">
<PhotoAlbum :photos="photos" />
</PhotoGroup>
<button @click="group?.open(0)">Open first</button>
<button @click="group?.openById('ocean')">Open ocean</button>
</template>PhotoGroup refs expose open, openById, and close. Use a descendant
useLightbox() call when you also need navigation and reactive state.
Controller inside a provider subtree
<!-- GalleryToolbar.vue -->
<script setup lang="ts">
import { useLightbox } from '@lupinum/nuxt-photo/app'
const lightbox = useLightbox()
</script>
<template>
<button @click="lightbox.prev()">Previous</button>
<button @click="lightbox.next()">Next</button>
<button @click="lightbox.close()">Close</button>
</template>Render this component below <PhotoGroup> or <LightboxProvider>. Vue
injection flows from parent to child, not within the same setup function.
Deep links
<script setup lang="ts">
import { useLightbox } from '@lupinum/nuxt-photo/app'
const lightbox = useLightbox()
const route = useRoute()
const router = useRouter()
onMounted(async () => {
const id = route.query.photo
if (typeof id !== 'string') return
try {
await lightbox.openById(id)
} catch (error) {
if (!(error instanceof RangeError)) throw error
await router.replace({ query: { ...route.query, photo: undefined } })
}
})
watch([lightbox.isOpen, lightbox.activePhoto], ([open, photo]) => {
void router.replace({ query: { ...route.query, photo: open ? photo?.id : undefined } })
})
</script>next() and prev() intentionally do nothing while closed. close() is
idempotent. Provider-level configuration is setup-time; remount with a new
key when it changes.