zumly 0.9.11 → 0.92.0
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.
- package/README.md +325 -133
- package/dist/zumly.css +309 -22
- package/dist/zumly.js +7 -0
- package/dist/zumly.min.css +1 -5
- package/dist/zumly.mjs +6 -770
- package/docs/DRIVER_API.md +340 -0
- package/package.json +52 -41
- package/src/drivers/driver-helpers.js +315 -0
- package/src/drivers/index.js +49 -0
- package/dist/zumly.min.mjs +0 -6
- package/dist/zumly.umd.js +0 -809
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { showViewContent } from '../view-visibility.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared helpers for Zumly transition drivers.
|
|
5
|
+
*
|
|
6
|
+
* These utilities handle the repetitive DOM work that every driver needs:
|
|
7
|
+
* applying final states, removing views, parsing durations, and running
|
|
8
|
+
* lateral (same-level) transitions. Import what you need; skip what you don't.
|
|
9
|
+
*
|
|
10
|
+
* @module driver-helpers
|
|
11
|
+
* @see ../../docs/DRIVER_API.md for the full driver authoring guide
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ─── Duration parsing ────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a CSS-style duration string to milliseconds.
|
|
18
|
+
* Accepts "1s", "500ms", "0.3s", or a raw number (treated as ms).
|
|
19
|
+
* Returns a safe fallback (500ms) for garbage input.
|
|
20
|
+
*
|
|
21
|
+
* @param {string|number} duration
|
|
22
|
+
* @returns {number} Duration in milliseconds (≥ 0)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* parseDurationMs('1s') // → 1000
|
|
26
|
+
* parseDurationMs('200ms') // → 200
|
|
27
|
+
* parseDurationMs(300) // → 300
|
|
28
|
+
* parseDurationMs('nope') // → 500
|
|
29
|
+
*/
|
|
30
|
+
export function parseDurationMs (duration) {
|
|
31
|
+
if (typeof duration === 'number' && !Number.isNaN(duration)) return Math.max(0, duration)
|
|
32
|
+
const str = String(duration)
|
|
33
|
+
const m = str.match(/^(\d+(?:\.\d+)?)\s*(ms|s)?$/i)
|
|
34
|
+
if (!m) return 500
|
|
35
|
+
const val = parseFloat(m[1])
|
|
36
|
+
const unit = (m[2] || 's').toLowerCase()
|
|
37
|
+
return unit === 'ms' ? Math.max(0, val) : Math.max(0, val * 1000)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse duration to seconds (convenience for libs like GSAP that use seconds).
|
|
42
|
+
*
|
|
43
|
+
* @param {string|number} duration
|
|
44
|
+
* @returns {number} Duration in seconds (≥ 0)
|
|
45
|
+
*/
|
|
46
|
+
export function parseDurationSec (duration) {
|
|
47
|
+
return parseDurationMs(duration) / 1000
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── End-state application ───────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Apply the final zoom-in state to an element based on its current class.
|
|
54
|
+
* This is the DOM cleanup that runs after the animation finishes.
|
|
55
|
+
*
|
|
56
|
+
* What it does for each role:
|
|
57
|
+
* - `is-new-current-view` → becomes `is-current-view`, no-events removed, final transform applied
|
|
58
|
+
* - `is-previous-view` → no-events removed, final transform applied
|
|
59
|
+
* - `is-last-view` → no-events removed, final transform applied
|
|
60
|
+
*
|
|
61
|
+
* @param {HTMLElement} element - The view element
|
|
62
|
+
* @param {object} currentStage - The snapshot with `views[]` array
|
|
63
|
+
*/
|
|
64
|
+
export function applyZoomInEndState (element, currentStage) {
|
|
65
|
+
if (element.classList.contains('is-new-current-view')) {
|
|
66
|
+
const v = currentStage.views[0].forwardState
|
|
67
|
+
element.classList.replace('is-new-current-view', 'is-current-view')
|
|
68
|
+
element.classList.remove('zoom-current-view', 'has-no-events')
|
|
69
|
+
element.style.transformOrigin = v.origin
|
|
70
|
+
element.style.transform = v.transform
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
if (element.classList.contains('is-previous-view')) {
|
|
74
|
+
const v = currentStage.views[1].forwardState
|
|
75
|
+
element.classList.remove('zoom-previous-view', 'has-no-events')
|
|
76
|
+
element.style.transformOrigin = v.origin
|
|
77
|
+
element.style.transform = v.transform
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (element.classList.contains('is-last-view')) {
|
|
81
|
+
const v = currentStage.views[2].forwardState
|
|
82
|
+
element.classList.remove('zoom-last-view', 'has-no-events')
|
|
83
|
+
element.style.transformOrigin = v.origin
|
|
84
|
+
element.style.transform = v.transform
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Apply the final zoom-out state to the previous view (it becomes current).
|
|
90
|
+
*
|
|
91
|
+
* @param {HTMLElement} element
|
|
92
|
+
* @param {{ origin?: string, transform: string }} backwardState
|
|
93
|
+
*/
|
|
94
|
+
export function applyZoomOutPreviousState (element, backwardState) {
|
|
95
|
+
element.classList.remove('zoom-previous-view-reverse', 'has-no-events', 'has-effect', 'has-effect-reverse')
|
|
96
|
+
element.style.removeProperty('--z-effect-filter')
|
|
97
|
+
element.style.transformOrigin = '0 0'
|
|
98
|
+
element.style.transform = backwardState.transform
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Apply the final zoom-out state to the last view (it becomes previous).
|
|
103
|
+
*
|
|
104
|
+
* @param {HTMLElement} element
|
|
105
|
+
* @param {{ origin: string, transform: string }} backwardState
|
|
106
|
+
*/
|
|
107
|
+
export function applyZoomOutLastState (element, backwardState) {
|
|
108
|
+
element.classList.remove('zoom-last-view-reverse', 'has-no-events', 'has-effect', 'has-effect-reverse')
|
|
109
|
+
element.style.removeProperty('--z-effect-filter')
|
|
110
|
+
element.style.transformOrigin = backwardState.origin
|
|
111
|
+
element.style.transform = backwardState.transform
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── DOM removal ─────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Safely remove a view from the canvas. Handles wrapped elements (e.g. Svelte
|
|
118
|
+
* components that get an extra parent div).
|
|
119
|
+
*
|
|
120
|
+
* @param {HTMLElement} element - The view to remove
|
|
121
|
+
* @param {HTMLElement} canvas - The canvas container
|
|
122
|
+
*/
|
|
123
|
+
export function removeViewFromCanvas (element, canvas) {
|
|
124
|
+
try {
|
|
125
|
+
if (canvas) canvas.removeChild(element)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
try {
|
|
128
|
+
if (element.parentElement) canvas.removeChild(element.parentElement)
|
|
129
|
+
} catch (e2) {
|
|
130
|
+
// Element already removed or re-parented — safe to ignore.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Visibility helpers ──────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Make a view visible and interactive before animating it.
|
|
139
|
+
* Views start hidden (class `hide` for opacity, `contentVisibility: hidden` via view-visibility)
|
|
140
|
+
* when inserted by prepareAndInsertView. Drivers must call this before running any animation.
|
|
141
|
+
*
|
|
142
|
+
* @param {...HTMLElement} elements - One or more view elements (nulls are skipped)
|
|
143
|
+
*/
|
|
144
|
+
export function showViews (...elements) {
|
|
145
|
+
for (const el of elements) {
|
|
146
|
+
if (!el) continue
|
|
147
|
+
el.classList.remove('hide')
|
|
148
|
+
showViewContent(el)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Lateral (same-level) transition ─────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Instant lateral navigation: swap current view without animation.
|
|
156
|
+
* This is the shared implementation that drivers use as a baseline.
|
|
157
|
+
* Drivers that want animated lateral transitions can override this.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} spec - Full lateral spec from the engine
|
|
160
|
+
* @param {function} onComplete - Must be called when done
|
|
161
|
+
*/
|
|
162
|
+
export function runLateralInstant (spec, onComplete) {
|
|
163
|
+
const {
|
|
164
|
+
currentView: incomingView,
|
|
165
|
+
previousView: outgoingView,
|
|
166
|
+
backView,
|
|
167
|
+
backViewState,
|
|
168
|
+
lastView,
|
|
169
|
+
lastViewState,
|
|
170
|
+
incomingTransformEnd,
|
|
171
|
+
currentStage,
|
|
172
|
+
canvas
|
|
173
|
+
} = spec
|
|
174
|
+
const v0 = currentStage.views[0]
|
|
175
|
+
|
|
176
|
+
showViews(incomingView)
|
|
177
|
+
incomingView.classList.replace('is-new-current-view', 'is-current-view')
|
|
178
|
+
incomingView.classList.remove('zoom-current-view', 'has-no-events')
|
|
179
|
+
incomingView.style.transformOrigin = v0.forwardState.origin
|
|
180
|
+
incomingView.style.transform = incomingTransformEnd || v0.forwardState.transform
|
|
181
|
+
|
|
182
|
+
if (backView && backViewState) backView.style.transform = backViewState.transformEnd
|
|
183
|
+
if (lastView && lastViewState) lastView.style.transform = lastViewState.transformEnd
|
|
184
|
+
|
|
185
|
+
if (!spec.keepAlive) removeViewFromCanvas(outgoingView, canvas)
|
|
186
|
+
onComplete()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─── Safety timeout ──────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Default safety buffer (ms) beyond the parsed duration. Ensures onComplete
|
|
193
|
+
* fires even if animationend / Promise.all never settles (element removed,
|
|
194
|
+
* duration 0, browser quirk, etc.).
|
|
195
|
+
*/
|
|
196
|
+
export const SAFETY_BUFFER_MS = 150
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create a finish-once guard: returns a `finish()` function that only runs
|
|
200
|
+
* the first time it's called, and clears the safety timer.
|
|
201
|
+
*
|
|
202
|
+
* Use this to avoid the "completed" flag boilerplate in every driver.
|
|
203
|
+
*
|
|
204
|
+
* @param {function} cleanup - The actual cleanup + onComplete work
|
|
205
|
+
* @param {number} timeoutMs - Safety timeout duration
|
|
206
|
+
* @returns {{ finish: function, safetyTimer: number }}
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* const { finish, safetyTimer } = createFinishGuard(() => {
|
|
210
|
+
* cancelAnimations()
|
|
211
|
+
* applyFinalState()
|
|
212
|
+
* onComplete()
|
|
213
|
+
* }, durationMs + SAFETY_BUFFER_MS)
|
|
214
|
+
*
|
|
215
|
+
* // In your animation's onComplete:
|
|
216
|
+
* animation.onfinish = finish
|
|
217
|
+
*
|
|
218
|
+
* // The safetyTimer ensures finish() runs even if onfinish never fires.
|
|
219
|
+
*/
|
|
220
|
+
export function createFinishGuard (cleanup, timeoutMs) {
|
|
221
|
+
let completed = false
|
|
222
|
+
const safetyTimer = setTimeout(() => {
|
|
223
|
+
if (!completed) { completed = true; cleanup() }
|
|
224
|
+
}, timeoutMs)
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
finish () {
|
|
228
|
+
if (completed) return
|
|
229
|
+
completed = true
|
|
230
|
+
clearTimeout(safetyTimer)
|
|
231
|
+
cleanup()
|
|
232
|
+
},
|
|
233
|
+
safetyTimer
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Matrix interpolation toolkit ────────────────────────────────────
|
|
238
|
+
// Used by matrix-based drivers (anime, motion, or any custom driver that
|
|
239
|
+
// needs to interpolate transforms through computed matrices).
|
|
240
|
+
|
|
241
|
+
/** Identity CSS matrix components. */
|
|
242
|
+
export function identityMatrix () {
|
|
243
|
+
return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse a CSS matrix() string into components.
|
|
248
|
+
* @param {string} mStr - e.g. "matrix(1, 0, 0, 1, 100, 50)"
|
|
249
|
+
* @returns {{ a, b, c, d, e, f }}
|
|
250
|
+
*/
|
|
251
|
+
export function parseMatrixString (mStr) {
|
|
252
|
+
if (!mStr || mStr === 'none') return identityMatrix()
|
|
253
|
+
const m = String(mStr).match(
|
|
254
|
+
/matrix\(([-\d.eE]+),\s*([-\d.eE]+),\s*([-\d.eE]+),\s*([-\d.eE]+),\s*([-\d.eE]+),\s*([-\d.eE]+)\)/
|
|
255
|
+
)
|
|
256
|
+
if (!m) return identityMatrix()
|
|
257
|
+
return {
|
|
258
|
+
a: parseFloat(m[1]) || 1,
|
|
259
|
+
b: parseFloat(m[2]) || 0,
|
|
260
|
+
c: parseFloat(m[3]) || 0,
|
|
261
|
+
d: parseFloat(m[4]) || 1,
|
|
262
|
+
e: parseFloat(m[5]) || 0,
|
|
263
|
+
f: parseFloat(m[6]) || 0,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Convert matrix components back to a CSS matrix() string.
|
|
269
|
+
* @param {{ a, b, c, d, e, f }} m
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
export function matrixToString (m) {
|
|
273
|
+
return `matrix(${m.a}, ${m.b}, ${m.c}, ${m.d}, ${m.e}, ${m.f})`
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Linear interpolation between two numbers. */
|
|
277
|
+
export function lerp (a, b, t) {
|
|
278
|
+
return a + (b - a) * t
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Interpolate between two matrix objects.
|
|
283
|
+
* @param {{ a,b,c,d,e,f }} from
|
|
284
|
+
* @param {{ a,b,c,d,e,f }} to
|
|
285
|
+
* @param {number} t - Progress 0..1
|
|
286
|
+
* @returns {{ a,b,c,d,e,f }}
|
|
287
|
+
*/
|
|
288
|
+
export function interpolateMatrix (from, to, t) {
|
|
289
|
+
const tt = Math.max(0, Math.min(1, t))
|
|
290
|
+
return {
|
|
291
|
+
a: lerp(from.a, to.a, tt),
|
|
292
|
+
b: lerp(from.b, to.b, tt),
|
|
293
|
+
c: lerp(from.c, to.c, tt),
|
|
294
|
+
d: lerp(from.d, to.d, tt),
|
|
295
|
+
e: lerp(from.e, to.e, tt),
|
|
296
|
+
f: lerp(from.f, to.f, tt),
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Read the browser-computed matrix for a given transform + origin pair.
|
|
302
|
+
* ⚠️ Forces a reflow (getBoundingClientRect). Use sparingly — once per
|
|
303
|
+
* element per animation setup, not per frame.
|
|
304
|
+
*
|
|
305
|
+
* @param {HTMLElement} element
|
|
306
|
+
* @param {string} origin - CSS transform-origin
|
|
307
|
+
* @param {string} transformStr - CSS transform value
|
|
308
|
+
* @returns {{ a,b,c,d,e,f }}
|
|
309
|
+
*/
|
|
310
|
+
export function readComputedMatrix (element, origin, transformStr) {
|
|
311
|
+
element.style.transformOrigin = origin
|
|
312
|
+
element.style.transform = transformStr
|
|
313
|
+
try { element.getBoundingClientRect() } catch {}
|
|
314
|
+
return parseMatrixString(getComputedStyle(element).transform)
|
|
315
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition driver factory.
|
|
3
|
+
* Returns a driver object with runTransition(spec, onComplete).
|
|
4
|
+
*
|
|
5
|
+
* Built-in drivers: 'css' (default), 'waapi', 'none', 'anime', 'gsap', 'motion'.
|
|
6
|
+
* Custom drivers: pass a function(spec, onComplete) directly.
|
|
7
|
+
*
|
|
8
|
+
* @param {string|function} driverOption
|
|
9
|
+
* @returns {{ runTransition: function }}
|
|
10
|
+
*/
|
|
11
|
+
import { runTransition as cssRun } from './css-transition.js'
|
|
12
|
+
import { runTransition as noneRun } from './none-transition.js'
|
|
13
|
+
import { runTransition as waapiRun } from './waapi-transition.js'
|
|
14
|
+
import { runTransition as animeRun } from './anime-transition.js'
|
|
15
|
+
import { runTransition as gsapRun } from './gsap-transition.js'
|
|
16
|
+
import { runTransition as motionRun } from './motion-transition.js'
|
|
17
|
+
|
|
18
|
+
const DRIVERS = {
|
|
19
|
+
css: cssRun,
|
|
20
|
+
waapi: waapiRun,
|
|
21
|
+
none: noneRun,
|
|
22
|
+
anime: animeRun,
|
|
23
|
+
gsap: gsapRun,
|
|
24
|
+
motion: motionRun,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getDriver (driverOption) {
|
|
28
|
+
if (typeof driverOption === 'function') {
|
|
29
|
+
return {
|
|
30
|
+
runTransition (spec, onComplete) {
|
|
31
|
+
driverOption(spec, onComplete)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const name = typeof driverOption === 'string' ? driverOption.toLowerCase() : 'css'
|
|
36
|
+
const run = DRIVERS[name] || DRIVERS.css
|
|
37
|
+
return { runTransition: run }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Named exports for direct imports and tests
|
|
41
|
+
export { runTransition as cssTransition } from './css-transition.js'
|
|
42
|
+
export { runTransition as noneTransition } from './none-transition.js'
|
|
43
|
+
export { runTransition as waapiTransition } from './waapi-transition.js'
|
|
44
|
+
export { runTransition as animeTransition } from './anime-transition.js'
|
|
45
|
+
export { runTransition as gsapTransition } from './gsap-transition.js'
|
|
46
|
+
export { runTransition as motionTransition } from './motion-transition.js'
|
|
47
|
+
|
|
48
|
+
// Re-export helpers for community drivers
|
|
49
|
+
export * from './driver-helpers.js'
|
package/dist/zumly.min.mjs
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* zumly v0.9.11
|
|
3
|
-
* Author Juan Martín Muda, @license MIT
|
|
4
|
-
* https://zumly.org
|
|
5
|
-
*/
|
|
6
|
-
function t(t){if(void 0!==t&&"none"===t[0].toLowerCase())return!0;if(void 0!==t&&t.length>0){return(t=>[...new Set(t)])(t.map(t=>t.toLowerCase())).every(t=>-1!==["blur","sepia","saturate"].indexOf(t))}}function e(t){var e="",n="";if(void 0!==t)return t.map(t=>{e+=""+("blur"===t.toLowerCase()?"blur(0px) ":"sepia"===t.toLowerCase()?"sepia(0) ":"saturate"===t.toLowerCase()?"saturate(0) ":"none"),n+=""+("blur"===t.toLowerCase()?"blur(0.8px) ":"sepia"===t.toLowerCase()?"sepia(5) ":"saturate"===t.toLowerCase()?"saturate(8) ":"none")}),[e,n]}function n(t,e,n){t[e]=n}function s(t,e,s,i,o={isRequired:!1,defaultValue:0,allowedValues:0,hasValidation:0,hasAssignFunction:0}){var a=`'${e}' property is required when instance is defined`,d=`'${e}' property has problems`,c=void 0!==s,l=void 0!==o.defaultValue,u=void 0!==o.hasValidation,h=void 0!==o.hasAssignFunction;if("string"===i||"object"===i||"boolean"===i)var m=typeof s===i;else"array"===i&&(m=Array.isArray(s));o.isRequired&&(c&&m?n(t,e,s):r(!1,a,"error")),!l||u||h||(c&&m?n(t,e,s):void 0===s?n(t,e,o.defaultValue):r(!1,d,"error")),u&&l&&!h&&(c&&m&&o.hasValidation?n(t,e,s):void 0===s?n(t,e,o.defaultValue):r(!1,d,"error")),u&&l&&h&&(c&&m&&o.hasValidation?n(t,e,o.hasAssignFunction):void 0===s?n(t,e,o.defaultValue):r(!1,d,"error"))}function i(t,e,n){const s=e;[{name:"current-view",stage:s.views[0]},{name:"previous-view",stage:s.views[1]},{name:"last-view",stage:s.views[2]}].map(e=>{"zoomOut"===t&&void 0!==e.stage&&(document.documentElement.style.setProperty(`--${e.name}-transform-start-${n}`,e.stage.forwardState.transform),document.documentElement.style.setProperty(`--${e.name}-transform-end-${n}`,e.stage.backwardState.transform),document.documentElement.style.setProperty(`--${e.name}-transformOrigin-start-${n}`,e.stage.forwardState.origin),document.documentElement.style.setProperty(`--${e.name}-transformOrigin-end-${n}`,e.stage.backwardState.origin),document.documentElement.style.setProperty(`--${e.name}-opacity-start-${n}`,1),document.documentElement.style.setProperty(`--${e.name}-filter-start-${n}`,e.stage.forwardState.filter),document.documentElement.style.setProperty(`--${e.name}-filter-end-${n}`,e.stage.backwardState.filter),"current-view"===e.name?(document.documentElement.style.setProperty("--zoom-duration-"+n,e.stage.backwardState.duration),document.documentElement.style.setProperty("--zoom-ease-"+n,e.stage.backwardState.ease),document.documentElement.style.setProperty(`--${e.name}-opacity-end-${n}`,0),document.documentElement.style.setProperty(`--${e.name}-filter-start-${n}`,"none"),document.documentElement.style.setProperty(`--${e.name}-filter-end-${n}`,"none")):document.documentElement.style.setProperty(`--${e.name}-opacity-end-${n}`,1)),"zoomIn"===t&&void 0!==e.stage&&(document.documentElement.style.setProperty(`--${e.name}-transform-start-${n}`,e.stage.backwardState.transform),document.documentElement.style.setProperty(`--${e.name}-transform-end-${n}`,e.stage.forwardState.transform),document.documentElement.style.setProperty(`--${e.name}-transformOrigin-start-${n}`,e.stage.backwardState.origin),document.documentElement.style.setProperty(`--${e.name}-transformOrigin-end-${n}`,e.stage.forwardState.origin),document.documentElement.style.setProperty(`--${e.name}-filter-start-${n}`,e.stage.backwardState.filter),document.documentElement.style.setProperty(`--${e.name}-filter-end-${n}`,e.stage.forwardState.filter),"current-view"===e.name?(document.documentElement.style.setProperty("--zoom-duration-"+n,e.stage.forwardState.duration),document.documentElement.style.setProperty("--zoom-ease-"+n,e.stage.forwardState.ease),document.documentElement.style.setProperty(`--${e.name}-opacity-start-${n}`,0),document.documentElement.style.setProperty(`--${e.name}-filter-start-${n}`,"none"),document.documentElement.style.setProperty(`--${e.name}-filter-end-${n}`,"none")):document.documentElement.style.setProperty(`--${e.name}-opacity-start-${n}`,1),document.documentElement.style.setProperty(`--${e.name}-opacity-end-${n}`,1))})}async function o(t,e,n,s,i){var o=null;o=s?t:t.dataset.to;var r=document.createElement("template");if("object"==typeof n[o]&&void 0!==n[o].render)r.innerHTML=await n[o].render();else if("function"==typeof n[o]){var a=document.createElement("div");new n[o]({target:a,context:i,props:t.dataset});a.classList.add("z-view"),r.content.appendChild(a)}else r.innerHTML=n[o];let d=r.content.querySelector(".z-view");s?d.classList.add("is-current-view"):(d.classList.add("is-new-current-view"),d.classList.add("has-no-events"),d.classList.add("hide"),d.classList.add("performance")),d.style.transformOrigin="0 0",d.dataset.viewName=o;await e.append(r.content);"object"==typeof n[o]&&void 0!==n[o].mounted&&"function"==typeof n[o].mounted()&&await n[o].mounted()}function r(t,e,n){e&&"welcome"===n&&console.info("%c Zumly %c "+e,"background: #424085; color: white; border-radius: 3px;","color: #424085"),e&&t&&("info"===n||void 0===n)&&console.info("%c Zumly %c "+e,"background: #6679A3; color: #304157; border-radius: 3px;","color: #6679A3"),e&&"warn"===n&&console.warn("%c Zumly %c "+e,"background: #DCBF53; color: #424085; border-radius: 3px;","color: #424085"),e&&"error"===n&&console.error("%c Zumly %c "+e,"background: #BE4747; color: white; border-radius: 3px;","color: #424085")}window.requestIdleCallback=window.requestIdleCallback||function(t){var e=Date.now();return setTimeout(()=>{t({didTimeout:!1,timeRemaining:()=>Math.max(0,50-(Date.now()-e))})},1)};class a{constructor(i){var o,d;this.instance=a.counter,this.storedViews=[],this.currentStage=null,this.storedPreviousScale=[1],this.trace=[],this.blockEvents=!1,this.touchstartX=0,this.touchstartY=0,this.touchendX=0,this.touchendY=0,this.touching=!1,d=this,(o=i)&&"object"==typeof o?(n(d,"options",!0),s(d,"mount",o.mount,"string",{isRequired:!0}),s(d,"initialView",o.initialView,"string",{isRequired:!0}),s(d,"views",o.views,"object",{isRequired:!0}),s(d,"debug",o.debug,"boolean",{defaultValue:!1}),s(d,"componentContext",o.componentContext,"object",{isRequired:!1,defaultValue:new Map}),o.transitions&&"object"==typeof o.transitions?(s(d,"cover",o.transitions.cover,"string",{defaultValue:"width",hasValidation:()=>{["height","width"].indexOf(o.transitions.cover.toLowerCase())}}),s(d,"duration",o.transitions.duration,"string",{defaultValue:"1s"}),s(d,"ease",o.transitions.ease,"string",{defaultValue:"ease-in-out"}),s(d,"effects",o.transitions.effects,"array",{defaultValue:["none","none"],hasValidation:t(o.transitions.effects),hasAssignFunction:e(o.transitions.effects)})):(n(d,"cover","width"),n(d,"duration","1s"),n(d,"ease","ease-in-out"),n(d,"effects",["none","none"]))):r(!1,"'options' object has to be provided when instance is defined","error"),this.options?(this._onZoom=this.onZoom.bind(this),this._onZoomInHandlerStart=this.onZoomInHandlerStart.bind(this),this._onZoomInHandlerEnd=this.onZoomInHandlerEnd.bind(this),this._onZoomOutHandlerStart=this.onZoomOutHandlerStart.bind(this),this._onZoomOutHandlerEnd=this.onZoomOutHandlerEnd.bind(this),this._onTouchStart=this.onTouchStart.bind(this),this._onTouchEnd=this.onTouchEnd.bind(this),this._onKeyUp=this.onKeyUp.bind(this),this._onWeel=this.onWeel.bind(this),this.canvas=document.querySelector(this.mount),this.canvas.setAttribute("tabindex",0),this.canvas.addEventListener("mouseup",this._onZoom,!1),this.canvas.addEventListener("touchend",this._onZoom,!1),this.canvas.addEventListener("touchstart",this._onTouchStart,{passive:!0}),this.canvas.addEventListener("touchend",this._onTouchEnd,!1),this.canvas.addEventListener("keyup",this._onKeyUp,!1),this.canvas.addEventListener("wheel",this._onWeel,{passive:!0})):this.notify("is unable to start: no {options} have been passed to the Zumly's instance.","error")}storeViews(t){this.tracing("storedViews()"),this.storedViews.push(t)}setPreviousScale(t){this.tracing("setPreviousScale()"),this.storedPreviousScale.push(t)}tracing(t){if("ended"===t){const t=this.trace.map((t,e)=>""+(0===e?`Instance ${this.instance}: ${t}`:""+t)).join(" > ");this.notify(t),this.trace=[]}else this.trace.push(t)}static get counter(){return a._counter=(a._counter||0)+1,a._counter}notify(t,e){return r(this.debug,t,e)}zoomLevel(){return this.storedViews.length}async init(){this.options&&(this.tracing("init()"),function(t){var e=document.createElement("style");let n="";["current-view","previous-view","last-view"].map(e=>{n+=`\n .zoom-${e}-${t} {\n animation-name: zoom-${e}-${t};\n animation-duration: var(--zoom-duration-${t});\n animation-timing-function: var(--zoom-ease-${t});\n }\n @keyframes zoom-${e}-${t} {\n 0% {\n transform-origin: var(--${e}-transformOrigin-start-${t});\n transform: var(--${e}-transform-start-${t});\n opacity: var(--${e}-opacity-start-${t});\n filter: var(--${e}-filter-start-${t})\n }\n 100% {\n transform-origin: var(--${e}-transformOrigin-end-${t});\n transform: var(--${e}-transform-end-${t});\n opacity: var(--${e}-opacity-end-${t});\n filter: var(--${e}-filter-end-${t})\n }\n }\n `}),e.innerHTML=n,document.head.appendChild(e)}(this.instance),await o(this.initialView,this.canvas,this.views,"init",this.componentContext),this.storeViews({zoomLevel:this.storedViews.length,views:[{viewName:this.initialView,backwardState:{origin:"0 0",transform:""}}]}),this.notify(""+(this.instance>1?`instance nº ${this.instance} is active.`:`is running! Instance nº ${this.instance} is active. ${this.debug?"Debug is active, can be deactivate by setting 'debug: false' when you define the instance.":""}\n More tips & docs at https://zumly.org`),"welcome"))}async zoomIn(t){this.tracing("zoomIn()");var e=this.instance;const n=this.canvas,s=n.getBoundingClientRect();var r=s.left,a=s.top;const d=this.storedPreviousScale[this.storedPreviousScale.length-1];this.tracing("renderView()"),await o(t,n,this.views,!1,this.componentContext),t.classList.add("zoomed");const c=t.getBoundingClientRect();var l=n.querySelector(".is-new-current-view"),u=n.querySelector(".is-current-view"),h=n.querySelector(".is-previous-view"),m=n.querySelector(".is-last-view");null!==m&&n.removeChild(m);const v=l.getBoundingClientRect(),w=v.width/c.width,g=1/w,f=v.height/c.height,p=1/f;var y=t.dataset.withDuration||this.duration,$=t.dataset.withEease||this.ease,E=this.effects[0],S=this.effects[1],b=this.cover;if("width"===b)var L=w,z=g;else"height"===b&&(L=f,z=p);this.setPreviousScale(L);var V=`translate(${c.x-r+(c.width-v.width*z)/2}px, ${c.y-a+(c.height-v.height*z)/2}px) scale(${z})`;l.style.transform=V,u.classList.add("is-previous-view"),u.classList.remove("is-current-view");const k=u.getBoundingClientRect();var O=u.style.transform;u.style.transformOrigin=`${c.x+c.width/2-k.x}px ${c.y+c.height/2-k.y}px`;const P=s.width/2-c.width/2-c.x+k.x,Z=s.height/2-c.height/2-c.y+k.y,x=`translate(${P}px, ${Z}px) scale(${L})`;u.style.transform=x;var C=t.getBoundingClientRect(),H=`translate(${C.x-r+(C.width-v.width)/2}px, ${C.y-a+(C.height-v.height)/2}px)`;if(null!==h){h.classList.remove("is-previous-view"),h.classList.add("is-last-view");var _=h.style.transform,I=u.getBoundingClientRect();h.style.transform=`translate(${P-r}px, ${Z-a}px) scale(${L*d})`;var T=h.querySelector(".zoomed").getBoundingClientRect();h.style.transform=_,u.style.transform=O;var q=u.getBoundingClientRect(),R=`translate(${s.width/2-c.width/2-c.x+(q.x-T.x)+I.x-r+(I.width-T.width)/2}px, ${s.height/2-c.height/2-c.y+(q.y-T.y)+I.y-a+(I.height-T.height)/2}px) scale(${L*d})`}else u.style.transform=O;var Y={zoomLevel:this.storedViews.length,views:[]};const A=l?{viewName:l.dataset.viewName,backwardState:{origin:l.style.transformOrigin,duration:y,ease:$,transform:V,filter:E},forwardState:{origin:l.style.transformOrigin,duration:y,ease:$,transform:H,filter:S}}:null,B=u?{viewName:u.dataset.viewName,backwardState:{origin:u.style.transformOrigin,duration:y,ease:$,transform:O,filter:window.getComputedStyle(document.documentElement).getPropertyValue("--previous-view-filter-end-"+e)},forwardState:{origin:u.style.transformOrigin,duration:y,ease:$,transform:x,filter:S}}:null,N=h?{viewName:h.dataset.viewName,backwardState:{origin:h.style.transformOrigin,duration:y,ease:$,transform:_,filter:window.getComputedStyle(document.documentElement).getPropertyValue("--previous-view-filter-end-"+e)},forwardState:{origin:h.style.transformOrigin,duration:y,ease:$,transform:R,filter:S}}:null,j=m?{viewName:m}:null;null!==A&&Y.views.push(A),null!==B&&Y.views.push(B),null!==N&&Y.views.push(N),null!==j&&Y.views.push(j),this.storeViews(Y),this.currentStage=this.storedViews[this.storedViews.length-1],this.tracing("setCSSVariables()"),i("zoomIn",this.currentStage,this.instance),u.classList.add("performance"),null!==h&&h.classList.add("performance"),l.classList.remove("hide"),l.addEventListener("animationstart",this._onZoomInHandlerStart),l.addEventListener("animationend",this._onZoomInHandlerEnd),u.addEventListener("animationend",this._onZoomInHandlerEnd),null!==h&&h.addEventListener("animationend",this._onZoomInHandlerEnd),l.classList.add("zoom-current-view-"+e),u.classList.add("zoom-previous-view-"+e),null!==h&&h.classList.add("zoom-last-view-"+e)}zoomOut(){this.tracing("zoomOut()"),this.blockEvents=!0,this.storedPreviousScale.pop();var t=this.instance;const e=this.canvas;this.currentStage=this.storedViews[this.storedViews.length-1];const n=this.currentStage.views[3];var s=e.querySelector(".is-current-view"),o=e.querySelector(".is-previous-view"),r=e.querySelector(".is-last-view");(this.tracing("setCSSVariables()"),i("zoomOut",this.currentStage,this.instance),s.classList.remove("performance"),o.querySelector(".zoomed").classList.remove("zoomed"),o.classList.remove("is-previous-view"),o.classList.add("is-current-view"),o.classList.remove("performance"),null!==r&&(r.classList.add("performance"),r.classList.add("is-previous-view"),r.classList.remove("is-last-view"),r.classList.remove("hide")),void 0!==n)&&(e.prepend(n.viewName),e.querySelector(".z-view:first-child").classList.add("hide"));s.addEventListener("animationstart",this._onZoomOutHandlerStart),s.addEventListener("animationend",this._onZoomOutHandlerEnd),o.addEventListener("animationend",this._onZoomOutHandlerEnd),null!==r&&r.addEventListener("animationend",this._onZoomOutHandlerEnd),s.classList.add("zoom-current-view-"+t),o.classList.add("zoom-previous-view-"+t),null!==r&&r.classList.add("zoom-last-view-"+t),this.storedViews.pop()}onZoom(t){this.storedViews.length>1&&!this.blockEvents&&!t.target.classList.contains("zoom-me")&&null===t.target.closest(".is-current-view")&&!this.touching&&(this.tracing("onZoom()"),t.stopPropagation(),this.zoomOut()),this.blockEvents||!t.target.classList.contains("zoom-me")||this.touching||(this.tracing("onZoom()"),t.stopPropagation(),this.zoomIn(t.target))}onKeyUp(t){this.tracing("onKeyUp()"),"ArrowLeft"!==t.key&&"ArrowDown"!==t.key||(t.preventDefault(),this.storedViews.length>1&&!this.blockEvents?this.zoomOut():this.notify("is on level zero. Can't zoom out. Trigger: "+t.key,"warn")),"ArrowRight"!==t.key&&"ArrowUp"!==t.key||(t.preventDefault(),this.notify(t.key+"has not actions defined"))}onWeel(t){this.blockEvents||(this.tracing("onWeel()"),t.deltaY,t.deltaY>0&&this.storedViews.length>1&&!this.blockEvents&&this.zoomOut())}onTouchStart(t){this.tracing("onTouchStart()"),this.touching=!0,this.touchstartX=t.changedTouches[0].screenX,this.touchstartY=t.changedTouches[0].screenY}onTouchEnd(t){this.blockEvents||(this.tracing("onTouchEnd()"),this.touchendX=t.changedTouches[0].screenX,this.touchendY=t.changedTouches[0].screenY,this.handleGesture(t))}handleGesture(t){t.stopPropagation(),this.tracing("handleGesture()"),this.touchendX<this.touchstartX-30&&(this.storedViews.length>1&&!this.blockEvents?(this.tracing("swipe left"),this.zoomOut()):this.notify("is on level zero. Can't zoom out. Trigger: Swipe left","warn")),this.touchendY<this.touchstartY-10&&(this.storedViews.length>1&&!this.blockEvents?this.tracing("swipe up"):this.notify("is on level zero. Can't zoom out. Trigger: Swipe up","warn")),this.touchendY===this.touchstartY&&!this.blockEvents&&t.target.classList.contains("zoom-me")&&this.touching&&(this.touching=!1,this.tracing("tap"),t.preventDefault(),this.zoomIn(t.target)),this.touchendY===this.touchstartY&&this.storedViews.length>1&&!this.blockEvents&&!t.target.classList.contains("zoom-me")&&null===t.target.closest(".is-current-view")&&this.touching&&(this.touching=!1,this.tracing("tap"),this.zoomOut())}onZoomOutHandlerStart(t){this.tracing("onZoomOutHandlerStart()"),this.blockEvents=!0,t.target.removeEventListener("animationstart",this._onZoomOutHandlerStart)}onZoomOutHandlerEnd(t){this.tracing("onZoomOutHandlerEnd()");const e=t.target;var n=this.currentStage;if(e.removeEventListener("animationend",this._onZoomOutHandlerEnd),e.classList.contains("zoom-current-view-"+this.instance)){try{this.canvas.removeChild(e)}catch(t){console.debug("Error when trying to remove element after zoom out. Trying to remove its parent instead...");try{this.canvas.removeChild(e.parentElement)}catch(t){console.debug("Error when trying to remove elemont after zoom out:",t),console.debug("Element to remove was:",e)}}this.blockEvents=!1}if(e.classList.contains("zoom-previous-view-"+this.instance)){var s=n.views[1].backwardState.origin,i=n.views[1].backwardState.transform;e.classList.remove("performance"),e.classList.remove("zoom-previous-view-"+this.instance),e.style.transformOrigin=s,e.style.transform=i,e.style.filter="none",2===n.views.length&&this.tracing("ended")}e.classList.contains("zoom-last-view-"+this.instance)&&(s=n.views[2].backwardState.origin,i=n.views[2].backwardState.transform,e.classList.remove("performance"),e.classList.remove("zoom-last-view-"+this.instance),e.style.transformOrigin=s,e.style.transform=i,n.views.length>2&&this.tracing("ended"))}onZoomInHandlerStart(t){this.tracing("onZoomInHandlerStart()"),this.blockEvents=!0,t.target.removeEventListener("animationstart",this._onZoomInHandlerStart)}onZoomInHandlerEnd(t){this.tracing("onZoomInHandlerEnd()");const e=t.target;var n=this.currentStage;if(t.target.classList.contains("is-new-current-view")){this.blockEvents=!1;var s="current-view",i=n.views[0].forwardState.transform,o=n.views[0].forwardState.origin;e.classList.remove("is-new-current-view"),e.classList.add("is-current-view")}else t.target.classList.contains("is-previous-view")?(s="previous-view",i=n.views[1].forwardState.transform,o=n.views[1].forwardState.origin,2===n.views.length&&this.tracing("ended")):(s="last-view",i=n.views[2].forwardState.transform,o=n.views[2].forwardState.origin,n.views.length>2&&this.tracing("ended"));e.classList.remove("performance"),e.classList.remove(`zoom-${s}-${this.instance}`),e.classList.remove("has-no-events"),e.style.transformOrigin=o,e.style.transform=i,e.style.filter=window.getComputedStyle(document.documentElement).getPropertyValue(`--${s}-filter-end-${this.instance}`),e.removeEventListener("animationend",this._onZoomInHandlerEnd)}}export default a;
|