smooth flip animation on delete/archive for session stack
This commit is contained in:
1 parent
587101fb97
commit
5db7aa43aa
3 files changed
+37
-13
No files matched your search
+25
-9
@@ -1319,7 +1319,7 @@ function _playQueuedSessionReflowAnimation(){
|
||||
const before=_pendingSessionReflowPositions;
|
||||
_pendingSessionReflowPositions=null;
|
||||
if(!before||!before.size) return;
|
||||
const reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const reduce=_sessionPrefersReducedMotion();
|
||||
if(reduce) return;
|
||||
const list=$('sessionList');
|
||||
if(!list) return;
|
||||
@@ -1327,14 +1327,30 @@ function _playQueuedSessionReflowAnimation(){
|
||||
const oldTop=before.get(row.dataset.sid);
|
||||
if(oldTop===undefined) return;
|
||||
const delta=oldTop-row.getBoundingClientRect().top;
|
||||
if(Math.abs(delta)<1||typeof row.animate!=='function') return;
|
||||
const anim=row.animate(
|
||||
[{transform:`translateY(${delta}px)`},{transform:'translateY(0)'}],
|
||||
{duration:360,easing:'cubic-bezier(.2,.8,.2,1)'}
|
||||
);
|
||||
if(anim&&anim.finished) anim.finished.catch(()=>{});
|
||||
if(Math.abs(delta)<1) return;
|
||||
row.style.setProperty('--session-reflow-offset',delta+'px');
|
||||
row.classList.add('session-reflowing');
|
||||
row.getBoundingClientRect();
|
||||
row.style.setProperty('--session-reflow-offset','0px');
|
||||
let reflowCleared=false;
|
||||
const clearReflow=()=>{
|
||||
if(reflowCleared) return;
|
||||
reflowCleared=true;
|
||||
row.classList.remove('session-reflowing');
|
||||
row.style.removeProperty('--session-reflow-offset');
|
||||
row.removeEventListener('transitionend',onReflowEnd);
|
||||
};
|
||||
const onReflowEnd=(event)=>{
|
||||
if(event.propertyName==='transform') clearReflow();
|
||||
};
|
||||
row.addEventListener('transitionend',onReflowEnd);
|
||||
setTimeout(clearReflow,420);
|
||||
});
|
||||
}
|
||||
|
||||
function _sessionPrefersReducedMotion(){
|
||||
return Boolean(window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
||||
}
|
||||
const SESSION_VIRTUAL_ROW_HEIGHT = 52;
|
||||
const SESSION_VIRTUAL_BUFFER_ROWS = 12;
|
||||
const SESSION_VIRTUAL_THRESHOLD_ROWS = 80;
|
||||
@@ -1621,7 +1637,7 @@ function _appendSessionDuplicateAction(menu, session){
|
||||
|
||||
function _playSessionActionMenuEntrance(menu){
|
||||
if(!menu) return;
|
||||
const reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const reduce=_sessionPrefersReducedMotion();
|
||||
if(reduce) return;
|
||||
if(typeof menu.animate==='function'){
|
||||
try{
|
||||
@@ -3251,7 +3267,7 @@ function renderSessionListFromCache(){
|
||||
const _longPressDelay=400;
|
||||
const _swipeActionThreshold=144;
|
||||
const _swipeCancelRatio=0.75;
|
||||
const _committedSwipeDuration=420;
|
||||
const _committedSwipeDuration=_sessionPrefersReducedMotion()?0:420;
|
||||
const _clearLongPressTimer=()=>{
|
||||
if(_longPressTimer){clearTimeout(_longPressTimer);_longPressTimer=null;}
|
||||
if(!_longPressMenuOpened) el.classList.remove('long-pressing');
|
||||
|
||||
+4
-1
@@ -429,7 +429,7 @@
|
||||
and attention indicator (26x26 at right:6px) still need 40px reserved
|
||||
when they're visible — covered by the hover / streaming / unread /
|
||||
menu-open / focus-within rule below. */
|
||||
.session-item{padding:8px 8px;margin-bottom:2px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--muted);transition:background .15s,color .15s,transform .5s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;display:flex;align-items:flex-start;gap:8px;min-width:0;position:relative;touch-action:pan-y;-webkit-tap-highlight-color:transparent;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;transform:translateX(var(--session-swipe-offset,0));}
|
||||
.session-item{padding:8px 8px;margin-bottom:2px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--muted);transition:background .15s,color .15s,transform .5s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;display:flex;align-items:flex-start;gap:8px;min-width:0;position:relative;touch-action:pan-y;-webkit-tap-highlight-color:transparent;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;transform:translateX(var(--session-swipe-offset,0)) translateY(var(--session-reflow-offset,0));}
|
||||
.session-item.streaming,.session-item.unread,.session-item:focus-within,.session-item.menu-open{padding-right:40px;}
|
||||
@media (hover:hover){.session-item:hover{background:var(--hover-bg);color:var(--text);}}
|
||||
.session-item.loading{background:var(--hover-bg);color:var(--text);}
|
||||
@@ -491,6 +491,7 @@
|
||||
line-height:1;
|
||||
}
|
||||
.session-item.dragging{transition:background .15s,color .15s,box-shadow .15s ease;}
|
||||
.session-item.session-reflowing{transition:background .15s,color .15s,transform .36s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;will-change:transform;}
|
||||
.session-item.swipe-committed{transition:background .15s,color .15s,transform .42s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;}
|
||||
.session-item.swipe-removing{
|
||||
min-height:0!important;
|
||||
@@ -585,6 +586,8 @@
|
||||
.session-action-opt.danger .ws-opt-icon,.session-action-opt.danger .ws-opt-name{color:var(--error);}
|
||||
@media (prefers-reduced-motion:reduce){
|
||||
.session-action-menu{animation:none;will-change:auto;}
|
||||
.session-item,.session-item.session-reflowing,.session-item.swipe-committed,.session-item.swipe-removing{transition:none;}
|
||||
.session-item.long-pressing{animation:none;}
|
||||
}
|
||||
/* Hide overlay during inline rename */
|
||||
.session-item:has(.session-title-input) .session-actions{display:none;}
|
||||
|
||||
@@ -25,6 +25,8 @@ def test_session_menu_has_subtle_open_animation():
|
||||
assert "@keyframes session-menu-in" in STYLE_CSS
|
||||
assert "@media (prefers-reduced-motion:reduce)" in STYLE_CSS
|
||||
assert ".session-action-menu{animation:none;will-change:auto;}" in STYLE_CSS
|
||||
assert ".session-item,.session-item.session-reflowing,.session-item.swipe-committed,.session-item.swipe-removing{transition:none;}" in STYLE_CSS
|
||||
assert ".session-item.long-pressing{animation:none;}" in STYLE_CSS
|
||||
|
||||
|
||||
def test_mobile_session_menu_opens_from_long_press_and_hides_dots():
|
||||
@@ -59,7 +61,7 @@ def test_session_swipes_archive_right_and_delete_left():
|
||||
assert "_swipeTracking=true" in SESSIONS_JS
|
||||
assert "const _trackHorizontalSwipe=(dx,dy)=>{" in SESSIONS_JS
|
||||
assert "_swipeActionThreshold=144" in SESSIONS_JS
|
||||
assert "_committedSwipeDuration=420" in SESSIONS_JS
|
||||
assert "_committedSwipeDuration=_sessionPrefersReducedMotion()?0:420" in SESSIONS_JS
|
||||
assert "const _handleSessionSwipe=(signedDx,signedDy)=>{" in SESSIONS_JS
|
||||
assert "if(_isSessionSwipeTarget()&&(_swipeTracking||Math.abs(signedDx)>Math.abs(signedDy))) _paintSessionSwipe(signedDx)" in SESSIONS_JS
|
||||
assert "if(_isSessionSwipeTarget()&&(_swipeTracking||dx>dy)) _paintSessionSwipe(signedDx)" in SESSIONS_JS
|
||||
@@ -128,12 +130,15 @@ def test_session_removal_reflows_surviving_rows_smoothly():
|
||||
assert "function _captureSessionReflowPositions(){" in SESSIONS_JS
|
||||
assert "positions.set(row.dataset.sid,row.getBoundingClientRect().top);" in SESSIONS_JS
|
||||
assert "function _playQueuedSessionReflowAnimation(){" in SESSIONS_JS
|
||||
assert "window.matchMedia('(prefers-reduced-motion: reduce)').matches" in SESSIONS_JS
|
||||
assert "function _sessionPrefersReducedMotion(){" in SESSIONS_JS
|
||||
assert "const delta=oldTop-row.getBoundingClientRect().top;" in SESSIONS_JS
|
||||
assert "{duration:360,easing:'cubic-bezier(.2,.8,.2,1)'}" in SESSIONS_JS
|
||||
assert "row.style.setProperty('--session-reflow-offset',delta+'px')" in SESSIONS_JS
|
||||
assert "row.classList.add('session-reflowing')" in SESSIONS_JS
|
||||
assert "row.style.setProperty('--session-reflow-offset','0px')" in SESSIONS_JS
|
||||
assert SESSIONS_JS.count("const reflowPositions=_captureSessionReflowPositions();") >= 2
|
||||
assert SESSIONS_JS.count("_pendingSessionReflowPositions=reflowPositions;") >= 2
|
||||
assert "_playQueuedSessionReflowAnimation();" in SESSIONS_JS
|
||||
assert ".session-item.session-reflowing{transition:background .15s,color .15s,transform .36s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;will-change:transform;}" in STYLE_CSS
|
||||
|
||||
|
||||
def test_ios_touch_events_drive_session_swipes():
|
||||
|
||||
Reference in new issue
Block a user