zumly 0.92.4 → 0.92.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Web Animations API transition driver.
3
+ * Uses element.animate() — no external dependencies.
4
+ *
5
+ * @param {Object} spec - Transition spec from the engine
6
+ * @param {function} onComplete - MUST be called exactly once when done
7
+ */
8
+ import {
9
+ parseDurationMs,
10
+ showViews,
11
+ applyZoomInEndState,
12
+ applyZoomOutPreviousState,
13
+ applyZoomOutLastState,
14
+ removeViewFromCanvas,
15
+ createFinishGuard,
16
+ SAFETY_BUFFER_MS,
17
+ } from './driver-helpers.js'
18
+
19
+ // Re-export for tests that import it directly
20
+ export { parseDurationMs }
21
+
22
+ export function runTransition (spec, onComplete) {
23
+ const { type, currentView, previousView, lastView, currentStage, duration, ease, canvas } = spec
24
+
25
+ if (!currentView || !previousView || !currentStage) {
26
+ onComplete()
27
+ return
28
+ }
29
+
30
+ const durationMs = parseDurationMs(duration)
31
+
32
+ if (type === 'lateral') {
33
+ runLateralAnimated(spec, durationMs, ease, onComplete)
34
+ return
35
+ }
36
+
37
+ if (type === 'zoomIn') {
38
+ runZoomIn(currentView, previousView, lastView, currentStage, durationMs, ease, onComplete)
39
+ return
40
+ }
41
+
42
+ if (type === 'zoomOut') {
43
+ runZoomOut(currentView, previousView, lastView, currentStage, durationMs, ease, canvas, onComplete)
44
+ return
45
+ }
46
+
47
+ onComplete()
48
+ }
49
+
50
+ // ─── Zoom In ─────────────────────────────────────────────────────────
51
+
52
+ function runZoomIn (currentView, previousView, lastView, currentStage, durationMs, ease, onComplete) {
53
+ showViews(currentView, previousView, lastView)
54
+
55
+ const v0 = currentStage.views[0]
56
+ const v1 = currentStage.views[1]
57
+ const v2 = lastView && currentStage.views[2] ? currentStage.views[2] : null
58
+
59
+ // Set initial state from snapshot
60
+ currentView.style.transformOrigin = v0.backwardState.origin
61
+ currentView.style.transform = v0.backwardState.transform
62
+ previousView.style.transformOrigin = v1.backwardState.origin
63
+ previousView.style.transform = v1.backwardState.transform
64
+ if (v2) {
65
+ lastView.style.transformOrigin = v2.backwardState.origin
66
+ lastView.style.transform = v2.backwardState.transform
67
+ }
68
+
69
+ // Start WAAPI animations with stagger delay
70
+ const stagger = currentStage.stagger || 0
71
+ const anims = []
72
+ anims.push(currentView.animate(
73
+ [{ transform: v0.backwardState.transform }, { transform: v0.forwardState.transform }],
74
+ { duration: durationMs, easing: ease, fill: 'forwards' }
75
+ ))
76
+ anims.push(previousView.animate(
77
+ [{ transform: v1.backwardState.transform }, { transform: v1.forwardState.transform }],
78
+ { duration: durationMs, delay: stagger, easing: ease, fill: 'forwards' }
79
+ ))
80
+ if (v2) {
81
+ anims.push(lastView.animate(
82
+ [{ transform: v2.backwardState.transform }, { transform: v2.forwardState.transform }],
83
+ { duration: durationMs, delay: stagger * 2, easing: ease, fill: 'forwards' }
84
+ ))
85
+ }
86
+
87
+ const maxDelay = v2 ? stagger * 2 : stagger
88
+ const { finish } = createFinishGuard(() => {
89
+ cancelAll(anims)
90
+ try {
91
+ applyZoomInEndState(currentView, currentStage)
92
+ applyZoomInEndState(previousView, currentStage)
93
+ if (lastView) applyZoomInEndState(lastView, currentStage)
94
+ } catch (e) { /* DOM cleanup should not block onComplete */ }
95
+ onComplete()
96
+ }, durationMs + maxDelay + SAFETY_BUFFER_MS)
97
+
98
+ Promise.all(anims.map(a => a.finished)).then(finish).catch(finish)
99
+ }
100
+
101
+ // ─── Zoom Out ────────────────────────────────────────────────────────
102
+
103
+ function runZoomOut (currentView, previousView, lastView, currentStage, durationMs, ease, canvas, onComplete) {
104
+ const v0 = currentStage.views[0]
105
+ const v1 = currentStage.views[1]
106
+ const v2 = lastView && currentStage.views[2] ? currentStage.views[2] : null
107
+ const to1 = v1.backwardState
108
+ const to2 = v2 ? v2.backwardState : null
109
+
110
+ // Set current view initial state
111
+ currentView.style.transformOrigin = v0.forwardState.origin
112
+ currentView.style.transform = v0.forwardState.transform
113
+
114
+ // Defer animation start to ensure computed styles are flushed
115
+ requestAnimationFrame(() => {
116
+ requestAnimationFrame(() => {
117
+ previousView.style.transformOrigin = v1.forwardState.origin
118
+ if (lastView && v2) lastView.style.transformOrigin = v2.forwardState.origin
119
+
120
+ // Use computed transform as "from" so animation runs from actual position
121
+ const from1 = previousView.style.transform || getComputedStyle(previousView).transform || v1.forwardState.transform
122
+ const from2 = lastView && v2
123
+ ? (lastView.style.transform || getComputedStyle(lastView).transform || v2.forwardState.transform)
124
+ : null
125
+
126
+ const stagger = currentStage.stagger || 0
127
+ const anims = []
128
+ anims.push(currentView.animate(
129
+ [{ transform: v0.forwardState.transform }, { transform: v0.backwardState.transform }],
130
+ { duration: durationMs, easing: ease, fill: 'forwards' }
131
+ ))
132
+ anims.push(previousView.animate(
133
+ [{ transform: from1 }, { transform: to1.transform }],
134
+ { duration: durationMs, delay: stagger, easing: ease, fill: 'both' }
135
+ ))
136
+ if (lastView && to2) {
137
+ anims.push(lastView.animate(
138
+ [{ transform: from2 }, { transform: to2.transform }],
139
+ { duration: durationMs, delay: stagger * 2, easing: ease, fill: 'both' }
140
+ ))
141
+ }
142
+
143
+ // Let WAAPI drive transforms — remove inline styles so WAAPI fill takes over
144
+ previousView.style.removeProperty('transform')
145
+ if (lastView) lastView.style.removeProperty('transform')
146
+
147
+ const maxDelay = (lastView && to2) ? stagger * 2 : stagger
148
+ const { finish } = createFinishGuard(() => {
149
+ cancelAll(anims)
150
+ try {
151
+ removeViewFromCanvas(currentView, canvas)
152
+ applyZoomOutPreviousState(previousView, to1)
153
+ if (lastView && to2) applyZoomOutLastState(lastView, to2)
154
+ } catch (e) { /* DOM cleanup should not block onComplete */ }
155
+ onComplete()
156
+ }, durationMs + maxDelay + SAFETY_BUFFER_MS)
157
+
158
+ Promise.all(anims.map(a => a.finished)).then(finish).catch(finish)
159
+ })
160
+ })
161
+ }
162
+
163
+ // ─── Lateral ────────────────────────────────────────────────────────
164
+
165
+ function runLateralAnimated (spec, durationMs, ease, onComplete) {
166
+ const {
167
+ currentView: incomingView,
168
+ previousView: outgoingView,
169
+ backView,
170
+ backViewState,
171
+ lastView,
172
+ lastViewState,
173
+ incomingTransformStart,
174
+ incomingTransformEnd,
175
+ outgoingTransform,
176
+ outgoingTransformEnd,
177
+ currentStage,
178
+ canvas
179
+ } = spec
180
+ const v0 = currentStage.views[0]
181
+
182
+ showViews(incomingView)
183
+ incomingView.classList.replace('is-new-current-view', 'is-current-view')
184
+ incomingView.classList.remove('zoom-current-view', 'has-no-events')
185
+ incomingView.style.transformOrigin = v0.forwardState.origin
186
+
187
+ const anims = []
188
+
189
+ // Incoming view slides in
190
+ anims.push(incomingView.animate(
191
+ [{ transform: incomingTransformStart, opacity: 0 }, { transform: incomingTransformEnd, opacity: 1 }],
192
+ { duration: durationMs, easing: ease, fill: 'forwards' }
193
+ ))
194
+
195
+ // Outgoing view slides out (skip animation in 'visible' keepAlive mode)
196
+ if (spec.keepAlive !== 'visible') {
197
+ anims.push(outgoingView.animate(
198
+ [{ transform: outgoingTransform, opacity: 1 }, { transform: outgoingTransformEnd, opacity: 0 }],
199
+ { duration: durationMs, easing: ease, fill: 'forwards' }
200
+ ))
201
+ }
202
+
203
+ // Back view shifts
204
+ if (backView && backViewState) {
205
+ anims.push(backView.animate(
206
+ [{ transform: backViewState.transformStart }, { transform: backViewState.transformEnd }],
207
+ { duration: durationMs, easing: ease, fill: 'forwards' }
208
+ ))
209
+ }
210
+
211
+ // Last view shifts
212
+ if (lastView && lastViewState) {
213
+ anims.push(lastView.animate(
214
+ [{ transform: lastViewState.transformStart }, { transform: lastViewState.transformEnd }],
215
+ { duration: durationMs, easing: ease, fill: 'forwards' }
216
+ ))
217
+ }
218
+
219
+ const { finish } = createFinishGuard(() => {
220
+ cancelAll(anims)
221
+ // Apply final state
222
+ incomingView.style.transform = incomingTransformEnd || v0.forwardState.transform
223
+ if (backView && backViewState) backView.style.transform = backViewState.transformEnd
224
+ if (lastView && lastViewState) lastView.style.transform = lastViewState.transformEnd
225
+ if (spec.keepAlive) {
226
+ outgoingView.style.opacity = ''
227
+ outgoingView.style.transform = outgoingTransformEnd
228
+ } else {
229
+ removeViewFromCanvas(outgoingView, canvas)
230
+ }
231
+ onComplete()
232
+ }, durationMs + SAFETY_BUFFER_MS)
233
+
234
+ Promise.all(anims.map(a => a.finished)).then(finish).catch(finish)
235
+ }
236
+
237
+ // ─── Helpers ─────────────────────────────────────────────────────────
238
+
239
+ function cancelAll (anims) {
240
+ for (const a of anims) {
241
+ try { a.cancel() } catch (e) { /* already finished or removed */ }
242
+ }
243
+ }
package/types/zumly.d.ts CHANGED
@@ -77,7 +77,7 @@ export interface ViewEntry {
77
77
 
78
78
  /** Zoom snapshot stored in the storedViews stack. */
79
79
  export interface ZoomSnapshot {
80
- level: number
80
+ zoomLevel: number
81
81
  views: [ViewEntry, ViewEntry, ViewEntry | null, ViewEntry | null]
82
82
  scale?: number
83
83
  stagger?: number
@@ -98,8 +98,10 @@ export interface TransitionOptions {
98
98
  effects?: [string] | [string, string]
99
99
  /** Progressive delay (ms) between view layers during zoom. Default: 0. */
100
100
  stagger?: number
101
- /** Parallax intensity (0-1). Default: 0. */
101
+ /** Accepted for compatibility but not applied yet (always treated as 0). */
102
102
  parallax?: number
103
+ /** Hide the trigger element during zoom: 'fade' (crossfade), true (hide), or false. Default: false. */
104
+ hideTrigger?: 'fade' | boolean
103
105
  }
104
106
 
105
107
  /** Lateral navigation UI configuration. */
@@ -194,8 +196,6 @@ export interface ZumlyOptions {
194
196
  inputs?: InputsOptions
195
197
  /** Enable deferred rendering (view content inserted after zoom animation). */
196
198
  deferred?: boolean
197
- /** Hide trigger mode during zoom: 'fade', 'remove', or falsy. */
198
- hideTrigger?: string | false
199
199
  }
200
200
 
201
201
  // ─── Events ─────────────────────────────────────────────────────────