Every feature is observable: gallery.on(event, fn) returns an unsubscribe
function.
const off = gallery.on('afterSlide', ({ from, to }) => console.log(from, '→', to));
off(); // stop listening
None of these are cancelable right now — fn just receives a
plain detail object, not a real Event; calling .preventDefault() on
it would throw. The before*/after* naming is a lifecycle-order
convention, not an implemented cancel mechanism — that's a documented future intent, not
current behavior, so don't rely on being able to stop something from a before*
handler today.
Core lifecycle
Event
Detail
Fires
beforeOpen
{ index }
Right at the start of open(), before any DOM change.
open
{ index }
After the lightbox DOM is built/shown, before the zoom-in transition starts.
afterOpen
{ index }
Same tick as open currently — kept distinct for future use.
beforeClose
{}
First thing inside close(), before anything (including zoom/rotate plugins
resetting their own state) reacts.
close / afterClose
{}
Once the zoom-out-to-thumbnail animation finishes (or immediately, under
prefers-reduced-motion or with no origin thumbnail to animate to).
destroy
{}
End of destroy(), after all teardown — the last event this bus ever emits.
captionModalChange
{ open }
The truncated-caption "read the rest" modal opening/closing — unrelated to the gallery's
own open/close above, despite the similar shape. Autoplay's
onCaptionExpand (Settings) reacts to
open: true.
Slides & items
Event
Detail
Fires
beforeSlide / afterSlide
{ from, to }
Around every navigation — buttons, keyboard, goTo(), a completed swipe.
slideItemLoad
{ index }
Once the active slide's media has actually settled (image decoded, or video's
loadedmetadata) — including instantly, if it was already preloaded. Never
fires for a background/preloading neighbor that isn't active yet.
itemsUpdated
{ items }
After
updateSlides()/addSlides()/removeSlides()/refresh()
change the item list.
Auto-hide
Event
Detail
Fires
controls:hide / controls:show
{}
Every auto-hide transition — sync points for a plugin with its own overlay that should
fade along with the built-in controls. See
gallery.controlsHidden on the Methods guide
for the current-state equivalent.
Gesture relay
Core drives horizontal drag-to-navigate and vertical drag-to-close itself — no event for
either. These have no built-in effect at all; they exist purely so a plugin (the Zoom plugin
consumes all of them) can react without core depending on that plugin existing. Coordinates
are viewport (client) pixels.
Event
Detail
tap
{ x, y, controlsWereHidden } — controlsWereHidden is a
snapshot from before this same tap's own pointerdown-triggered controls reveal
ran (a live read of gallery.controlsHidden at tap time would always say
false, since that reveal already happened by then). Useful for a plugin
that wants to tell "this tap was just revealing hidden controls" apart from "this tap
landed on an already-visible gallery."
doubleTap
{ x, y }
pinchStart
{ centerX, centerY }
pinchMove
{ scale, centerX, centerY } — scale is relative to
this pinch's own start distance (1 = unchanged), not cumulative across separate
pinches.
pinchEnd
{}
wheelZoom
{ deltaScale, x, y } — deltaScale is a small per-event
increment (positive = zoom in); a consumer accumulates it, it isn't an absolute scale.
Plugin-emitted
Event
Detail
From
autoplayStart / autoplayStop
{}
Autoplay
fullscreenChange
{ fullscreen }
Fullscreen — from the native fullscreenchange event, not the toolbar
button's click, so it reflects reality even when fullscreen was entered/exited by other
means (browser Escape, a request rejected).
rotateFlipChange
{ index, flipH, flipV, rotation }
RotateFlip, on every click — the plugin never persists this itself (resets per slide);
store the payload yourself if you need to keep it.
zoomChange
{ index, scale }
Zoom, on every scale change (gesture or button-driven) — same non-persisted pattern.
layoutRender
{ tiles: { index, element }[] }
Layout, after a render pass builds/appends tile DOM — tiles is exactly what
was built this pass, for injecting custom content (a badge, a selection
checkbox) without re-deriving which tiles are new. See the
Layout guide.
Plugin commands
The opposite direction from everything above: instead of listening for a plugin's state
change, a custom plugin's own button can command one of these four to act —
ctx.emit('requestZoomIn', {}) — without importing the target plugin at all. Each
one mirrors its real toolbar button exactly: same function, same edge-case handling. A no-op
if the target plugin isn't loaded.
Command
Effect
Handled by
requestAutoplayStart
Starts the slideshow — a no-op while already playing.
Autoplay
requestAutoplayStop
Stops the slideshow — a no-op while already stopped.
Autoplay
requestZoomIn / requestZoomOut
Same fixed step as the toolbar buttons/w/s shortcuts.
Zoom
requestZoomActualSize
Toggles between the image's native resolution and neutral, same as the button.
Zoom
requestZoomReset
Returns straight to neutral from any zoom level — no toolbar-button equivalent of its
own (requestZoomActualSize only resets conditionally).
Zoom
requestRotateLeft / requestRotateRight
Same 90° step as the toolbar buttons.
RotateFlip
requestFlipHorizontal / requestFlipVertical
Same toggle as the toolbar buttons.
RotateFlip
requestFullscreenToggle
Enters or exits fullscreen, same as the toolbar button.