finally swipe to delete behaves like ios one

This commit is contained in:
corgifist 2025-08-04 20:55:35 +03:00
parent 824a12b991
commit 27860244e5
4 changed files with 35 additions and 32 deletions

View File

@ -267,7 +267,10 @@ const ProjectDropdown = ({
<div className="flex flex-row items-center w-[95%]"> <div className="flex flex-row items-center w-[95%]">
<SheetTitle className="font-semibold line-clamp-1">{project.title}</SheetTitle> <SheetTitle className="font-semibold line-clamp-1">{project.title}</SheetTitle>
<SheetClose asChild> <SheetClose asChild>
<Button variant="ghost" onClick={() => setRenameDialogOpen(true)}> <Button variant="ghost" onClick={(e) => {
e.preventDefault();
setRenameDialogOpen(true);
}}>
<PencilIcon /><span className="sr-only">Rename</span> <PencilIcon /><span className="sr-only">Rename</span>
</Button> </Button>
</SheetClose> </SheetClose>
@ -463,11 +466,7 @@ const ProjectContainer = ({
</SwipeToDelete> </SwipeToDelete>
</div> </div>
) : ( ) : (
<div className="w-[100% + 5 * var(--spacing)] -mx-5 overflow-hidden"> projectComponent
<div ref={containerRef} className="w-full bg-background px-5 py-2">
{projectComponent}
</div>
</div>
); );
}; };

View File

@ -73,12 +73,6 @@ main {
height: 100%; height: 100%;
} }
.no-scroll {
overflow: hidden;
height: 100%; /* Optional, but often recommended for full-page scroll prevention */
touch-action: none;
}
.dark body { .dark body {
background-color: var(--color-background); background-color: var(--color-background);
} }

View File

@ -20,7 +20,7 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
deleteText = 'Delete', deleteText = 'Delete',
fadeOnDeletion = true, fadeOnDeletion = true,
useBoldDeleteFont = true, useBoldDeleteFont = true,
threshold = 70 threshold = 75
}) => { }) => {
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
const content = useRef<HTMLDivElement>(null); const content = useRef<HTMLDivElement>(null);
@ -82,17 +82,21 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
lastXRef.current = pageX; lastXRef.current = pageX;
lastYRef.current = pageY; lastYRef.current = pageY;
const raw = pageX - startX; const raw = pageX - startX;
let x = dragX < 0 ? rubber(raw) : rubber(raw, width * 0.1); let x = dragX < 0 ? rubber(raw) : rubber(raw, width * 0.1);
if ((Math.abs(dragX) === 0 ? Math.abs(vY) < window.innerHeight * 0.05 : true)) { if ((Math.abs(dragX) === 0 ? Math.abs(vY) < window.innerHeight * 0.05 && Math.abs(vX) > Math.abs(vY) : true)) {
if (x < 0) setAllowOverscroll(true); if (x < 0) setAllowOverscroll(true);
if (x <= 0 || (allowOverscroll && x >= 0)) setDragX(x); if (x <= 0 || (allowOverscroll && x >= 0)) {
setDragX(x);
document.body.classList.add('no-scroll');
}
} else { } else {
setDragX(0); setDragX(0);
setAllowOverscroll(false); setAllowOverscroll(false);
document.body.classList.remove('no-scroll');
} }
if (Math.abs(vY) < 20) document.body.classList.add('no-scroll');
}; };
const handleDelete = () => { const handleDelete = () => {
@ -110,19 +114,16 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
if (!dragging) return; if (!dragging) return;
setDragging(false); setDragging(false);
const shouldDelete = const shouldDelete = isSticky || velocity < -1000;
isSticky ||
velocity < -1000;
setAllowOverscroll(false); setAllowOverscroll(false);
document.body.classList.remove('no-scroll'); document.body.classList.remove('no-scroll');
if (Math.abs(velocityY) > window.innerHeight * 0.05) { if (Math.abs(velocityY) > window.innerHeight * 0.05 && Math.abs(dragX) < 20) {
setDragX(0);
setDragX(0); setDragX(0);
if (transparencyTimeout) { if (transparencyTimeout) {
clearTimeout(transparencyTimeout); clearTimeout(transparencyTimeout);
} }
setForceTransparentBackground(true); setForceTransparentBackground(true);
transparencyTimeout = setTimeout(() => setForceTransparentBackground(false), 150); transparencyTimeout = setTimeout(() => setForceTransparentBackground(false), 200);
return; return;
} }
if (!shouldDelete) { if (!shouldDelete) {
@ -130,14 +131,18 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
text.current?.classList.add('ios-ease'); text.current?.classList.add('ios-ease');
const textWidth = text.current ? text.current.getBoundingClientRect().width : 0; const textWidth = text.current ? text.current.getBoundingClientRect().width : 0;
if (((velocity < 0 && Math.abs(velocity) > 10) || dragX < -textWidth * 1.5 && velocity > 0) && text.current) { if (((velocity < 0 && Math.abs(velocity) > 10) || dragX < -textWidth * 1.5 && velocity > 0) && text.current) {
setDragX(-textWidth * 1.5); if (velocity < 0) {
} else if (allowOverscroll && dragX > 0) { setDragX(-textWidth * 1.5);
} else {
setDragX(0);
}
} else if (allowOverscroll && dragX > 0 && velocity > 0) {
setDragX(0); setDragX(0);
if (transparencyTimeout) { if (transparencyTimeout) {
clearTimeout(transparencyTimeout); clearTimeout(transparencyTimeout);
} }
setForceTransparentBackground(true); setForceTransparentBackground(true);
transparencyTimeout = setTimeout(() => setForceTransparentBackground(false), 150); transparencyTimeout = setTimeout(() => setForceTransparentBackground(false), 200);
} else setDragX(0); } else setDragX(0);
return; return;
} }
@ -217,7 +222,7 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
const deleteTransform = isCollapsing const deleteTransform = isCollapsing
? `translateX(calc(${dragX}px + 5rem))` ? `translateX(calc(${dragX}px + 5rem))`
: (isSticky ? `translateX(calc(${dragX}px + 5rem))` : `translateX(max(0rem, calc(${dragX}px + 5rem)))`); : (isSticky ? `translateX(calc(${dragX}px + 5rem))` : `translateX(max(0rem + ${dragX * 0.07}px, calc(${dragX}px + 5rem)))`);
const opacityTransparent = fadeOnDeletion ? 0 : 1; const opacityTransparent = fadeOnDeletion ? 0 : 1;
const backgroundTransparent = opacityTransparent == 0 ? 'transparent' : backgroundClass; const backgroundTransparent = opacityTransparent == 0 ? 'transparent' : backgroundClass;
@ -255,7 +260,8 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
marginTop: '1px', marginTop: '1px',
marginBottom: '1px', marginBottom: '1px',
paddingRight: '1rem', paddingRight: '1rem',
transition: dragX > 1 ? '' : 'background 300ms' transition: dragX > 1 ? '' : 'background 300ms',
willChange: 'background'
}} }}
> >
<button style={{ <button style={{
@ -264,7 +270,8 @@ const SwipeToDelete: FC<SwipeToDeleteProps> = ({
color: 'white', color: 'white',
fontWeight: useBoldDeleteFont ? 600 : '', fontWeight: useBoldDeleteFont ? 600 : '',
height: '100%', height: '100%',
opacity: isCollapsing ? opacityTransparent : 1 opacity: isCollapsing ? opacityTransparent : 1,
willChange: `transform, opacity`
}} onClick={handleDelete} ref={text}> }} onClick={handleDelete} ref={text}>
{deleteText} {deleteText}
</button> </button>

View File

@ -8,7 +8,10 @@
touch-action: none; touch-action: none;
} }
.font-sharpen { .no-scroll {
-webkit-transform: translateZ(0); overflow: hidden;
transform: translateZ(0); height: 100%;
/* Optional, but often recommended for full-page scroll prevention */
touch-action: none;
overscroll-behavior: none;
} }