@ -1,8 +1,7 @@
/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, T ask, T ransitionFn, TransitionManager } from '#client' */
/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, T ransitionFn, TransitionManager } from '#client' */
import { noop , is _function } from '../../../shared/utils.js' ;
import { noop , is _function } from '../../../shared/utils.js' ;
import { effect } from '../../reactivity/effects.js' ;
import { effect } from '../../reactivity/effects.js' ;
import { current _effect , untrack } from '../../runtime.js' ;
import { current _effect , untrack } from '../../runtime.js' ;
import { raf } from '../../timing.js' ;
import { loop } from '../../loop.js' ;
import { loop } from '../../loop.js' ;
import { should _intro } from '../../render.js' ;
import { should _intro } from '../../render.js' ;
import { current _each _item } from '../blocks/each.js' ;
import { current _each _item } from '../blocks/each.js' ;
@ -97,17 +96,10 @@ export function animation(element, get_fn, get_params) {
) {
) {
const options = get _fn ( ) ( this . element , { from , to } , get _params ? . ( ) ) ;
const options = get _fn ( ) ( this . element , { from , to } , get _params ? . ( ) ) ;
animation = animate (
animation = animate ( this . element , options , undefined , 1 , ( ) => {
this . element ,
options ,
undefined ,
1 ,
( ) => {
animation ? . abort ( ) ;
animation ? . abort ( ) ;
animation = undefined ;
animation = undefined ;
} ,
} ) ;
undefined
) ;
}
}
} ,
} ,
fix ( ) {
fix ( ) {
@ -192,14 +184,13 @@ export function transition(flags, element, get_fn, get_params) {
/** @type {Animation | undefined} */
/** @type {Animation | undefined} */
var outro ;
var outro ;
/** @type {(() => void) | undefined} */
var reset ;
function get _options ( ) {
function get _options ( ) {
// If a transition is still ongoing, we use the existing options rather than generating
// If a transition is still ongoing, we use the existing options rather than generating
// new ones. This ensures that reversible transitions reverse smoothly, rather than
// new ones. This ensures that reversible transitions reverse smoothly, rather than
// jumping to a new spot because (for example) a different `duration` was used
// jumping to a new spot because (for example) a different `duration` was used
return ( current _options ? ? = get _fn ( ) ( element , get _params ? . ( ) , { direction } ) ) ;
return ( current _options ? ? = get _fn ( ) ( element , get _params ? . ( ) ? ? /** @type {P} */ ( { } ) , {
direction
} ) ) ;
}
}
/** @type {TransitionManager} */
/** @type {TransitionManager} */
@ -208,65 +199,43 @@ export function transition(flags, element, get_fn, get_params) {
in ( ) {
in ( ) {
element . inert = inert ;
element . inert = inert ;
// abort the outro to prevent overlap with the intro
if ( ! is _intro ) {
outro ? . abort ( ) ;
outro ? . abort ( ) ;
// abort previous intro (can happen if an element is intro'd, then outro'd, then intro'd again)
outro ? . reset ? . ( ) ;
return ;
}
if ( ! is _outro ) {
// if we intro then outro then intro again, we want to abort the first intro,
// if it's not a bidirectional transition
intro ? . abort ( ) ;
intro ? . abort ( ) ;
}
if ( is _intro ) {
dispatch _event ( element , 'introstart' ) ;
dispatch _event ( element , 'introstart' ) ;
intro = animate (
element ,
intro = animate ( element , get _options ( ) , outro , 1 , ( ) => {
get _options ( ) ,
outro ,
1 ,
( ) => {
dispatch _event ( element , 'introend' ) ;
dispatch _event ( element , 'introend' ) ;
// Ensure we cancel the animation to prevent leaking
// Ensure we cancel the animation to prevent leaking
intro ? . abort ( ) ;
intro ? . abort ( ) ;
intro = current _options = undefined ;
intro = current _options = undefined ;
} ,
} ) ;
is _both
? undefined
: ( ) => {
intro = current _options = undefined ;
}
) ;
} else {
reset ? . ( ) ;
}
} ,
} ,
out ( fn ) {
out ( fn ) {
// abort previous outro (can happen if an element is outro'd, then intro'd, then outro'd again)
if ( ! is _outro ) {
outro ? . abort ( ) ;
fn ? . ( ) ;
current _options = undefined ;
return ;
}
if ( is _outro ) {
element . inert = true ;
element . inert = true ;
dispatch _event ( element , 'outrostart' ) ;
dispatch _event ( element , 'outrostart' ) ;
outro = animate (
element ,
get _options ( ) ,
intro ,
0 ,
( ) => {
dispatch _event ( element , 'outroend' ) ;
outro = current _options = undefined ;
fn ? . ( ) ;
} ,
is _both
? undefined
: ( ) => {
outro = current _options = undefined ;
}
) ;
// TODO arguably the outro should never null itself out until _all_ outros for this effect have completed...
outro = animate ( element , get _options ( ) , intro , 0 , ( ) => {
// in that case we wouldn't need to store `reset` separately
dispatch _event ( element , 'outroend' ) ;
reset = outro . reset ;
} else {
fn ? . ( ) ;
fn ? . ( ) ;
}
} ) ;
} ,
} ,
stop : ( ) => {
stop : ( ) => {
intro ? . abort ( ) ;
intro ? . abort ( ) ;
@ -282,7 +251,7 @@ export function transition(flags, element, get_fn, get_params) {
// parent (block) effect is where the state change happened. we can determine that by
// parent (block) effect is where the state change happened. we can determine that by
// looking at whether the block effect is currently initializing
// looking at whether the block effect is currently initializing
if ( is _intro && should _intro ) {
if ( is _intro && should _intro ) {
let run = is _global ;
var run = is _global ;
if ( ! run ) {
if ( ! run ) {
var block = /** @type {Effect | null} */ ( e . parent ) ;
var block = /** @type {Effect | null} */ ( e . parent ) ;
@ -311,17 +280,16 @@ export function transition(flags, element, get_fn, get_params) {
* @ param { AnimationConfig | ( ( opts : { direction : 'in' | 'out' } ) => AnimationConfig ) } options
* @ param { AnimationConfig | ( ( opts : { direction : 'in' | 'out' } ) => AnimationConfig ) } options
* @ param { Animation | undefined } counterpart The corresponding intro / outro to this outro / intro
* @ param { Animation | undefined } counterpart The corresponding intro / outro to this outro / intro
* @ param { number } t2 The target ` t ` value — ` 1 ` for intro , ` 0 ` for outro
* @ param { number } t2 The target ` t ` value — ` 1 ` for intro , ` 0 ` for outro
* @ param { ( ( ) => void ) | undefined } on _finish Called after successfully completing the animation
* @ param { ( ( ) => void ) } on _finish Called after successfully completing the animation
* @ param { ( ( ) => void ) | undefined } on _abort Called if the animation is aborted
* @ returns { Animation }
* @ returns { Animation }
* /
* /
function animate ( element , options , counterpart , t2 , on _finish , on _abort ) {
function animate ( element , options , counterpart , t2 , on _finish ) {
var is _intro = t2 === 1 ;
var is _intro = t2 === 1 ;
if ( is _function ( options ) ) {
if ( is _function ( options ) ) {
// In the case of a deferred transition (such as `crossfade`), `option` will be
// In the case of a deferred transition (such as `crossfade`), `option` will be
// a function rather than an `AnimationConfig`. We need to call this function
// a function rather than an `AnimationConfig`. We need to call this function
// once DOM has been updated...
// once the DOM has been updated...
/** @type {Animation} */
/** @type {Animation} */
var a ;
var a ;
var aborted = false ;
var aborted = false ;
@ -329,7 +297,7 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) {
queue _micro _task ( ( ) => {
queue _micro _task ( ( ) => {
if ( aborted ) return ;
if ( aborted ) return ;
var o = options ( { direction : is _intro ? 'in' : 'out' } ) ;
var o = options ( { direction : is _intro ? 'in' : 'out' } ) ;
a = animate ( element , o , counterpart , t2 , on _finish , on _abort );
a = animate ( element , o , counterpart , t2 , on _finish );
} ) ;
} ) ;
// ...but we want to do so without using `async`/`await` everywhere, so
// ...but we want to do so without using `async`/`await` everywhere, so
@ -341,14 +309,15 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) {
} ,
} ,
deactivate : ( ) => a . deactivate ( ) ,
deactivate : ( ) => a . deactivate ( ) ,
reset : ( ) => a . reset ( ) ,
reset : ( ) => a . reset ( ) ,
t : ( now ) => a . t ( now )
t : ( ) => a . t ( )
} ;
} ;
}
}
counterpart ? . deactivate ( ) ;
counterpart ? . deactivate ( ) ;
if ( ! options ? . duration ) {
if ( ! options ? . duration ) {
on _finish ? . ( ) ;
on _finish ( ) ;
return {
return {
abort : noop ,
abort : noop ,
deactivate : noop ,
deactivate : noop ,
@ -359,90 +328,73 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) {
const { delay = 0 , css , tick , easing = linear } = options ;
const { delay = 0 , css , tick , easing = linear } = options ;
var start = raf . now ( ) + delay ;
var keyframes = [ ] ;
var t1 = counterpart ? . t ( start ) ? ? 1 - t2 ;
var delta = t2 - t1 ;
var duration = options . duration * Math . abs ( delta ) ;
if ( is _intro && counterpart === undefined ) {
var end = start + duration ;
if ( tick ) {
tick ( 0 , 1 ) ; // TODO put in nested effect, to avoid interleaved reads/writes?
}
/** @type {globalThis.Animation} */
if ( css ) {
var animation ;
var styles = css _to _keyframe ( css ( 0 , 1 ) ) ;
keyframes . push ( styles , styles ) ;
}
}
/** @type {Task} */
var get _t = ( ) => 1 - t2 ;
var task ;
if ( css ) {
// create a dummy animation that lasts as long as the delay (but with whatever devtools
// run after a micro task so that all transitions that are lining up and are about to run can correctly measure the DOM
// multiplier is in effect). in the common case that it is `0`, we keep it anyway so that
queue _micro _task ( ( ) => {
// the CSS keyframes aren't created until the DOM is updated
// WAAPI
var animation = element . animate ( keyframes , { duration : delay } ) ;
animation . onfinish = ( ) => {
// for bidirectional transitions, we start from the current position,
// rather than doing a full intro/outro
var t1 = counterpart ? . t ( ) ? ? 1 - t2 ;
counterpart ? . abort ( ) ;
var delta = t2 - t1 ;
var duration = /** @type {number} */ ( options . duration ) * Math . abs ( delta ) ;
var keyframes = [ ] ;
var keyframes = [ ] ;
var n = Math . ceil ( duration / ( 1000 / 60 ) ) ; // `n` must be an integer, or we risk missing the `t2` value
// In case of a delayed intro, apply the initial style for the duration of the delay;
if ( css ) {
// else in case of a fade-in for example the element would be visible until the animation starts
var n = Math . ceil ( duration / ( 1000 / 60 ) ) ; // `n` must be an integer, or we risk missing the `t2` value
if ( is _intro && delay > 0 ) {
let m = Math . ceil ( delay / ( 1000 / 60 ) ) ;
let keyframe = css _to _keyframe ( css ( 0 , 1 ) ) ;
for ( let i = 0 ; i < m ; i += 1 ) {
keyframes . push ( keyframe ) ;
}
}
for ( var i = 0 ; i <= n ; i += 1 ) {
for ( var i = 0 ; i <= n ; i += 1 ) {
var t = t1 + delta * easing ( i / n ) ;
var t = t1 + delta * easing ( i / n ) ;
var styles = css ( t , 1 - t ) ;
var styles = css ( t , 1 - t ) ;
keyframes . push ( css _to _keyframe ( styles ) ) ;
keyframes . push ( css _to _keyframe ( styles ) ) ;
}
}
}
animation = element . animate ( keyframes , {
animation = element . animate ( keyframes , { duration , fill : 'forwards' } ) ;
delay : is _intro ? 0 : delay ,
duration : duration + ( is _intro ? delay : 0 ) ,
easing : 'linear' ,
fill : 'forwards'
} ) ;
animation . finished
animation . onfinish = ( ) => {
. then ( ( ) => {
get _t = ( ) => t2 ;
on _finish ? . ( ) ;
tick ? . ( t2 , 1 - t2 ) ;
on _finish ( ) ;
} ;
if ( t2 === 1 ) {
get _t = ( ) => {
animation . cancel ( ) ;
var time = /** @type {number} */ (
}
/** @type {globalThis.Animation} */ ( animation ) . currentTime
} )
) ;
. catch ( ( e ) => {
// Error for DOMException: The user aborted a request. This results in two things:
// - startTime is `null`
// - currentTime is `null`
// We can't use the existence of an AbortError as this error and error code is shared
// with other Web APIs such as fetch().
if ( animation . startTime !== null && animation . currentTime !== null ) {
return t1 + delta * easing ( time / duration ) ;
throw e ;
} ;
}
} ) ;
} ) ;
} else {
// Timer
if ( t1 === 0 ) {
tick ? . ( 0 , 1 ) ; // TODO put in nested effect, to avoid interleaved reads/writes?
}
task = loop ( ( now ) => {
if ( tick ) {
if ( now >= end ) {
loop ( ( ) => {
tick ? . ( t2 , 1 - t2 ) ;
if ( animation . playState !== 'running' ) return false ;
on _finish ? . ( ) ;
return false ;
}
if ( now >= start ) {
var t = get _t ( ) ;
var p = t1 + delta * easing ( ( now - start ) / duration ) ;
tick ( t , 1 - t ) ;
tick ? . ( p , 1 - p ) ;
}
return true ;
return true ;
} ) ;
} ) ;
}
}
} ;
return {
return {
abort : ( ) => {
abort : ( ) => {
@ -451,23 +403,15 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) {
// This prevents memory leaks in Chromium
// This prevents memory leaks in Chromium
animation . effect = null ;
animation . effect = null ;
}
}
task ? . abort ( ) ;
on _abort ? . ( ) ;
on _finish = undefined ;
on _abort = undefined ;
} ,
} ,
deactivate : ( ) => {
deactivate : ( ) => {
on _finish = undefined ;
on _finish = noop ;
on _abort = undefined ;
} ,
} ,
reset : ( ) => {
reset : ( ) => {
if ( t2 === 0 ) {
if ( t2 === 0 ) {
tick ? . ( 1 , 0 ) ;
tick ? . ( 1 , 0 ) ;
}
}
} ,
} ,
t : ( now ) => {
t : ( ) => get _t ( )
var t = t1 + delta * easing ( ( now - start ) / duration ) ;
return Math . min ( 1 , Math . max ( 0 , t ) ) ;
}
} ;
} ;
}
}