fix: improve mobile touch interactions by disabling default browser gestures and updating sidebar resizing logic
Build and Release / release (push) Successful in 37s

This commit is contained in:
taDuc
2026-06-04 20:12:04 +07:00
parent 35cd174c8b
commit 38b6e9fca6
4 changed files with 99 additions and 58 deletions
@@ -219,11 +219,13 @@ export default function ReplayPreviewLayerPanel({
borderRadius: 20, borderRadius: 20,
padding: "14px 10px", padding: "14px 10px",
width: 58, width: 58,
boxSizing: "border-box",
alignItems: "center", alignItems: "center",
boxShadow: "0 20px 48px rgba(2, 6, 23, 0.45)", boxShadow: "0 20px 48px rgba(2, 6, 23, 0.45)",
backdropFilter: "blur(12px)", backdropFilter: "blur(12px)",
maxHeight: "100%", maxHeight: "100%",
overflow: "hidden", overflowX: "hidden",
overflowY: "hidden",
}} }}
> >
{renderStyles()} {renderStyles()}
@@ -233,7 +235,9 @@ export default function ReplayPreviewLayerPanel({
style={{ style={{
flexGrow: 1, flexGrow: 1,
overflowY: "auto", overflowY: "auto",
overflowX: "hidden",
width: "100%", width: "100%",
boxSizing: "border-box",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
@@ -242,6 +242,21 @@ export default function PublicPreviewClientPage({
localStorage.setItem("timeline-year", String(timelineYear)); localStorage.setItem("timeline-year", String(timelineYear));
} }
}, [timelineYear]); }, [timelineYear]);
// Prevent global browser zoom on multi-touch pinch gestures for this entire route
useEffect(() => {
const preventZoom = (e: TouchEvent) => {
if (e.touches.length > 1) {
e.preventDefault();
}
};
document.addEventListener("touchmove", preventZoom, { passive: false });
return () => {
document.removeEventListener("touchmove", preventZoom);
};
}, []);
useEffect(() => { useEffect(() => {
if (!loadInteractiveMap) return; if (!loadInteractiveMap) return;
const timeoutId = window.setTimeout(() => { const timeoutId = window.setTimeout(() => {
@@ -408,12 +423,8 @@ export default function PublicPreviewClientPage({
if (isLargeScreen) { if (isLargeScreen) {
return "min(392px, calc(100vw - 120px))"; return "min(392px, calc(100vw - 120px))";
} }
if (isLayerPanelVisible) { return "min(280px, calc(100vw - 86px))";
return `calc(100vw - 104px)`; }, [isLargeScreen]);
} else {
return `calc(100vw - 36px)`;
}
}, [isLargeScreen, isLayerPanelVisible]);
const searchBarWrapperStyle = useMemo(() => { const searchBarWrapperStyle = useMemo(() => {
if (isLargeScreen) { if (isLargeScreen) {
+36 -8
View File
@@ -227,8 +227,14 @@ export default function TimelineBar({
onMouseDown={() => startChangingYear(-1)} onMouseDown={() => startChangingYear(-1)}
onMouseUp={stopChangingYear} onMouseUp={stopChangingYear}
onMouseLeave={stopChangingYear} onMouseLeave={stopChangingYear}
onTouchStart={() => startChangingYear(-1)} onTouchStart={(e) => {
onTouchEnd={stopChangingYear} e.preventDefault();
startChangingYear(-1);
}}
onTouchEnd={(e) => {
e.preventDefault();
stopChangingYear();
}}
disabled={effectiveDisabled} disabled={effectiveDisabled}
className={styles.adjustBtn} className={styles.adjustBtn}
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }} style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
@@ -264,8 +270,14 @@ export default function TimelineBar({
onMouseDown={() => startChangingYear(1)} onMouseDown={() => startChangingYear(1)}
onMouseUp={stopChangingYear} onMouseUp={stopChangingYear}
onMouseLeave={stopChangingYear} onMouseLeave={stopChangingYear}
onTouchStart={() => startChangingYear(1)} onTouchStart={(e) => {
onTouchEnd={stopChangingYear} e.preventDefault();
startChangingYear(1);
}}
onTouchEnd={(e) => {
e.preventDefault();
stopChangingYear();
}}
disabled={effectiveDisabled} disabled={effectiveDisabled}
className={styles.adjustBtn} className={styles.adjustBtn}
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }} style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
@@ -397,8 +409,14 @@ export default function TimelineBar({
onMouseDown={() => startChangingYear(-1)} onMouseDown={() => startChangingYear(-1)}
onMouseUp={stopChangingYear} onMouseUp={stopChangingYear}
onMouseLeave={stopChangingYear} onMouseLeave={stopChangingYear}
onTouchStart={() => startChangingYear(-1)} onTouchStart={(e) => {
onTouchEnd={stopChangingYear} e.preventDefault();
startChangingYear(-1);
}}
onTouchEnd={(e) => {
e.preventDefault();
stopChangingYear();
}}
disabled={effectiveDisabled} disabled={effectiveDisabled}
className={styles.adjustBtn} className={styles.adjustBtn}
title="Giảm 1 năm" title="Giảm 1 năm"
@@ -411,8 +429,14 @@ export default function TimelineBar({
onMouseDown={() => startChangingYear(1)} onMouseDown={() => startChangingYear(1)}
onMouseUp={stopChangingYear} onMouseUp={stopChangingYear}
onMouseLeave={stopChangingYear} onMouseLeave={stopChangingYear}
onTouchStart={() => startChangingYear(1)} onTouchStart={(e) => {
onTouchEnd={stopChangingYear} e.preventDefault();
startChangingYear(1);
}}
onTouchEnd={(e) => {
e.preventDefault();
stopChangingYear();
}}
disabled={effectiveDisabled} disabled={effectiveDisabled}
className={styles.adjustBtn} className={styles.adjustBtn}
title="Tăng 1 năm" title="Tăng 1 năm"
@@ -714,6 +738,8 @@ function CanvasTimelineRuler({
} }
}, [year, drawYear]); }, [year, drawYear]);
const handleWheel = (e: React.WheelEvent) => { const handleWheel = (e: React.WheelEvent) => {
if (disabled) return; if (disabled) return;
e.preventDefault(); e.preventDefault();
@@ -840,6 +866,7 @@ function CanvasTimelineRuler({
border: "1px solid rgba(255, 255, 255, 0.08)", border: "1px solid rgba(255, 255, 255, 0.08)",
overflow: "hidden", overflow: "hidden",
cursor: disabled ? "not-allowed" : "ew-resize", cursor: disabled ? "not-allowed" : "ew-resize",
touchAction: "none",
}} }}
onWheel={handleWheel} onWheel={handleWheel}
> >
@@ -849,6 +876,7 @@ function CanvasTimelineRuler({
display: "block", display: "block",
width: "100%", width: "100%",
height: "100%", height: "100%",
touchAction: "none",
}} }}
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove} onPointerMove={handlePointerMove}
+41 -43
View File
@@ -205,47 +205,7 @@ function PublicWikiSidebar({
window.addEventListener("pointerup", onUp); window.addEventListener("pointerup", onUp);
}; };
const handleHeightPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
event.preventDefault();
const startY = event.clientY;
const startHeight = sidebarHeight || 400;
// Tạo đường ghost ảo nằm ngang chỉ vị trí kéo chiều cao
const ghost = document.createElement("div");
ghost.style.position = "fixed";
ghost.style.left = "0";
ghost.style.right = "0";
ghost.style.height = "4px";
ghost.style.backgroundColor = "#38bdf8";
ghost.style.boxShadow = "0 0 12px rgba(56, 189, 248, 0.8)";
ghost.style.zIndex = "99999";
ghost.style.cursor = "row-resize";
ghost.style.pointerEvents = "none";
const startScreenY = window.innerHeight - startHeight;
ghost.style.top = `${startScreenY}px`;
document.body.appendChild(ghost);
const onMove = (e: PointerEvent) => {
ghost.style.top = `${e.clientY}px`;
};
const onUp = (e: PointerEvent) => {
window.removeEventListener("pointermove", onMove);
window.removeEventListener("pointerup", onUp);
if (ghost.parentNode) {
ghost.parentNode.removeChild(ghost);
}
const deltaY = startY - e.clientY;
const nextHeight = Math.max(200, Math.min(window.innerHeight * 0.9, startHeight + deltaY));
if (onSidebarHeightChange) {
onSidebarHeightChange(nextHeight);
}
};
window.addEventListener("pointermove", onMove);
window.addEventListener("pointerup", onUp);
};
const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null); const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
const processedWiki = useMemo(() => { const processedWiki = useMemo(() => {
@@ -327,6 +287,27 @@ function PublicWikiSidebar({
return () => root.removeEventListener("click", handleClick); return () => root.removeEventListener("click", handleClick);
}, [onWikiLinkRequest, renderHtml]); }, [onWikiLinkRequest, renderHtml]);
const isExpanded = useMemo(() => {
if (typeof window === "undefined") return false;
const fullHeight = Math.round(window.innerHeight * 0.70);
return (sidebarHeight || 400) >= fullHeight;
}, [sidebarHeight]);
const handleHeightToggle = () => {
if (typeof window === "undefined") return;
const halfHeight = Math.round(window.innerHeight * 0.45);
const fullHeight = Math.round(window.innerHeight * 0.85);
const currentHeight = sidebarHeight || 400;
const nextHeight = Math.abs(currentHeight - halfHeight) < Math.abs(currentHeight - fullHeight)
? fullHeight
: halfHeight;
if (onSidebarHeightChange) {
onSidebarHeightChange(nextHeight);
}
};
const [isMobileOrTablet, setIsMobileOrTablet] = useState(false); const [isMobileOrTablet, setIsMobileOrTablet] = useState(false);
useEffect(() => { useEffect(() => {
const checkDevice = () => setIsMobileOrTablet(window.innerWidth < 1024); const checkDevice = () => setIsMobileOrTablet(window.innerWidth < 1024);
@@ -359,17 +340,18 @@ function PublicWikiSidebar({
{/* Grab Handle for bottom sheet on mobile */} {/* Grab Handle for bottom sheet on mobile */}
{isMobileOrTablet ? ( {isMobileOrTablet ? (
<div <div
onPointerDown={handleHeightPointerDown} onClick={handleHeightToggle}
style={{ style={{
width: "100%", width: "100%",
height: 24, height: 28,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
cursor: "row-resize", cursor: "pointer",
zIndex: 60, zIndex: 60,
userSelect: "none", userSelect: "none",
flexShrink: 0, flexShrink: 0,
gap: 8,
}} }}
> >
<div <div
@@ -380,6 +362,22 @@ function PublicWikiSidebar({
backgroundColor: "rgba(255, 255, 255, 0.3)", backgroundColor: "rgba(255, 255, 255, 0.3)",
}} }}
/> />
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="rgba(255, 255, 255, 0.5)"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
style={{
transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.3s ease",
}}
>
<polyline points="18 15 12 9 6 15" />
</svg>
</div> </div>
) : null} ) : null}