Skip to content

Commit 5998b44

Browse files
committed
Refine picture grid success timing.
Align PicturePasswordGrid success animation timing with actual visual behavior so the promise resolves after the burst window, not just icon bounce. Keep reduced-motion users out of burst playback and make burst overlay positioning explicit. Update grid unit tests to validate burst visibility timing, reduced-motion behavior, and isolate animation tests by stubbing the burst component. Resolves #15097 AI-assisted-by: gpt-5
1 parent 1e8673e commit 5998b44

2 files changed

Lines changed: 35 additions & 12 deletions

File tree

kolibri/plugins/user_auth/frontend/views/SignInPage/PictureSignIn/PicturePasswordGrid.vue

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -302,32 +302,43 @@
302302
*/
303303
const playSuccessAnimation = () => {
304304
const STAGGER = 150;
305-
const DURATION = 1480;
305+
const ICON_BOUNCE_DURATION = 380;
306+
const BURST_DURATION = 1100;
306307
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
307308
const stagger = reduce ? 0 : STAGGER;
308-
const dur = reduce ? 0 : DURATION;
309+
const iconDuration = reduce ? 0 : ICON_BOUNCE_DURATION;
310+
const burstDuration = reduce ? 0 : BURST_DURATION;
311+
const iconCount = sequence.value.length;
312+
313+
if (!iconCount) {
314+
return Promise.resolve();
315+
}
309316
310317
return new Promise(resolve => {
311-
for (let i = 0; i < sequence.value.length; i++) {
318+
for (let i = 0; i < iconCount; i++) {
312319
const id = sequence.value[i];
313-
const isLast = i === sequence.value.length - 1;
320+
const isLast = i === iconCount - 1;
314321
window.setTimeout(() => {
315322
bouncingId.value = id;
316323
if (isLast) {
317324
arrowBouncing.value = true;
318-
burstVisible.value = true;
319-
window.setTimeout(() => {
320-
burstVisible.value = false;
321-
}, 1100);
325+
if (!reduce) {
326+
burstVisible.value = true;
327+
window.setTimeout(() => {
328+
burstVisible.value = false;
329+
}, burstDuration);
330+
}
322331
}
323332
window.setTimeout(() => {
324333
if (bouncingId.value === id) bouncingId.value = null;
325334
if (isLast) arrowBouncing.value = false;
326-
}, dur);
335+
}, iconDuration);
327336
}, i * stagger);
328337
}
329338
330-
window.setTimeout(() => resolve(), (sequence.value.length - 1) * stagger + dur);
339+
const lastStart = (iconCount - 1) * stagger;
340+
const animationDuration = Math.max(lastStart + iconDuration, lastStart + burstDuration);
341+
window.setTimeout(() => resolve(), animationDuration);
331342
});
332343
};
333344
@@ -496,7 +507,10 @@
496507
497508
.submit-burst {
498509
position: absolute;
510+
top: 50%;
511+
left: 50%;
499512
z-index: 100;
513+
transform: translate(-50%, -50%);
500514
}
501515
502516
.submit-icon {

kolibri/plugins/user_auth/frontend/views/SignInPage/PictureSignIn/__tests__/PicturePasswordGrid.spec.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ describe('PicturePasswordGrid', () => {
301301
iconStyle: 'colorful',
302302
showIconText: true,
303303
},
304-
stubs: ['PicturePasswordOption', 'KIcon'],
304+
stubs: ['PicturePasswordOption', 'KIcon', 'SubmitBurstAnimation'],
305305
});
306306
}
307307

@@ -355,12 +355,20 @@ describe('PicturePasswordGrid', () => {
355355
await nextTick();
356356
expect(wrapper.vm.bouncingId).toBe(3);
357357
expect(wrapper.vm.arrowBouncing).toBe(true);
358+
expect(wrapper.vm.burstVisible).toBe(true);
358359

359-
// After the final 380ms, bouncing clears and Promise resolves
360+
// After 380ms, icon/arrow bounce is done but burst is still running
360361
jest.advanceTimersByTime(380);
361362
await nextTick();
362363
expect(wrapper.vm.bouncingId).toBeNull();
363364
expect(wrapper.vm.arrowBouncing).toBe(false);
365+
expect(wrapper.vm.burstVisible).toBe(true);
366+
expect(resolved).toBe(false);
367+
368+
// Burst duration gates completion (1100ms from last icon start)
369+
jest.advanceTimersByTime(720);
370+
await nextTick();
371+
expect(wrapper.vm.burstVisible).toBe(false);
364372
expect(resolved).toBe(true);
365373
});
366374

@@ -389,6 +397,7 @@ describe('PicturePasswordGrid', () => {
389397
await nextTick();
390398
expect(resolved).toBe(true);
391399
expect(wrapper.vm.arrowBouncing).toBe(false);
400+
expect(wrapper.vm.burstVisible).toBe(false);
392401
});
393402
});
394403

0 commit comments

Comments
 (0)