- commit
- 0610a984d84d60970d9e6d5a2f9e30aa945419e5
- parent
- 0a66e0ad2a39035138b71ded8451977c40cb43b4
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 26 | +++++++++++++++++--------- |
| M | tests/ | 16 | ++++++++++++++++ |
@@ -49,18 +49,18 @@ function phase(now, start, duration) {
4949 return clamp((now - start) / Math.max(1, duration));5050 }5151 52-function settledFrame(tileCount) {52+function settledFrame(tileCount, exitProgress = 0) {5353 return {5454 elapsed: Number.POSITIVE_INFINITY,5555 ringProgress: 1,5656 cardProgress: 1,5757 cardScale: 1,58- cardOffset: 0,58+ cardOffset: 48 * easeIn(exitProgress),5959 tileProgress: Array.from({ length: tileCount }, () => 1),6060 barProgress: 1,6161 countProgress: 1,6262 actionsProgress: 1,63- exitProgress: 0,63+ exitProgress,6464 settled: true,6565 };6666 }@@ -86,14 +86,24 @@ export function winMotionFrame(elapsedOrOptions = {}, options = {}) {
8686 tileCount = 3,8787 } = input || {};8888 const count = Math.max(0, Number.isInteger(tileCount) ? tileCount : 3);89- if (reducedMotion || winSkip === true || skip === true) return settledFrame(count);90- const started = Number.isFinite(winSkip) && winSkip > winStart ? winSkip : winStart;89+ const entranceSkip = skip === true || winSkip === true || Number.isFinite(winSkip);90+ const started = winStart;9191 const virtualElapsed = Number.isFinite(elapsed)9292 ? elapsed9393 : Number.isFinite(now) && Number.isFinite(started)9494 ? now - started9595 : 0;9696 const frameNow = started + virtualElapsed;97+ const requestedExit = Number.isFinite(exitElapsed) || (winExitStart && Number.isFinite(winExitStart));98+ const exitProgress = Number.isFinite(exitElapsed)99+ ? phase(exitElapsed, 0, WIN_TIMING.exit)100+ : requestedExit101+ ? phase(frameNow, winExitStart, WIN_TIMING.exit)102+ : 0;103+ // Reduced motion resolves every requested primitive to its end state. A104+ // normal Enter skip settles the entrance but still lets the 170ms exit play.105+ if (reducedMotion) return settledFrame(count, requestedExit ? 1 : 0);106+ if (entranceSkip) return settledFrame(count, exitProgress);97107 const cardStart = started + WIN_TIMING.cardDelay;98108 const frame = {99109 elapsed: virtualElapsed,@@ -110,10 +120,8 @@ export function winMotionFrame(elapsedOrOptions = {}, options = {}) {
110120 exitProgress: 0,111121 settled: false,112122 };113- if (Number.isFinite(exitElapsed)) {114- frame.exitProgress = phase(exitElapsed, 0, WIN_TIMING.exit);115- } else if (winExitStart && Number.isFinite(winExitStart)) {116- frame.exitProgress = phase(frameNow, winExitStart, WIN_TIMING.exit);123+ if (requestedExit) {124+ frame.exitProgress = exitProgress;117125 frame.cardOffset = 48 * easeIn(frame.exitProgress);118126 }119127 if (frame.exitProgress >= 1) frame.settled = true;@@ -36,6 +36,22 @@ test('win skip and reduced motion settle all phases, while exit is bounded', ()
3636 assert.deepEqual(reduced.tileProgress, [1, 1, 1, 1]);3737 });3838 39+test('a skipped entrance still plays the normal exit timeline', () => {40+ const midExit = winMotionFrame({ now: 1200, winStart: 1000, winSkip: true, winExitStart: 1100 });41+ assert.equal(midExit.settled, false);42+ assert.equal(midExit.exitProgress > 0 && midExit.exitProgress < 1, true);43+ assert.equal(midExit.cardProgress, 1);44+ const finished = winMotionFrame({ now: 1300, winStart: 1000, skip: true, winExitStart: 1100 });45+ assert.equal(finished.exitProgress, 1);46+ assert.equal(finished.settled, true);47+});48+49+test('reduced motion settles an explicitly requested exit immediately', () => {50+ const reduced = winMotionFrame({ now: 1000, winStart: 900, reducedMotion: true, winExitStart: 1000 });51+ assert.equal(reduced.exitProgress, 1);52+ assert.equal(reduced.settled, true);53+});54+3955 test('win timeline also accepts stage-friendly elapsed and exit elapsed values', () => {4056 const frame = winMotionFrame(WIN_TIMING.cardDelay + 1, { tileCount: 3 });4157 assert.equal(frame.cardProgress > 0, true);