mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Move sidebar resize math into lib
The first press no longer pairs with an imaginary one at time zero.
This commit is contained in:
@@ -22,7 +22,6 @@
|
||||
import {
|
||||
commitSidebarWidth,
|
||||
discardSidebarWidthPreview,
|
||||
MIN_SIDEBAR_WIDTH,
|
||||
previewSidebarWidth,
|
||||
RAIL_WIDTH_REM,
|
||||
renderedSidebarWidth,
|
||||
@@ -33,6 +32,7 @@
|
||||
sidebarWidth,
|
||||
toggleSidebar,
|
||||
} from "@app/lib/sidebar.svelte";
|
||||
import { doublePressDetector, dragResult } from "@app/lib/sidebarResize";
|
||||
import { updateChecker } from "@app/lib/updateChecker.svelte";
|
||||
import useLocalStorage from "@app/lib/useLocalStorage.svelte";
|
||||
import { isMac } from "@app/lib/utils";
|
||||
@@ -92,14 +92,6 @@
|
||||
|
||||
const mini = $derived(collapsed.value);
|
||||
|
||||
// How far below the minimum width the drag has to go before it collapses,
|
||||
// rather than the edge just sticking at the minimum.
|
||||
const COLLAPSE_DRAG_SLACK_REM = 2;
|
||||
|
||||
// While collapsed, the rail follows the drag at a fraction of its distance so
|
||||
// it feels attached to the pointer, instead of sitting still until the
|
||||
// threshold and then jumping open.
|
||||
const RAIL_STRETCH_FACTOR = 0.35;
|
||||
let railStretch = $state(0);
|
||||
|
||||
// What `.slot` is animating towards.
|
||||
@@ -176,29 +168,14 @@
|
||||
// A second press in quick succession fits the sidebar. Detected here rather
|
||||
// than with `dblclick`, because the pointerdown below is cancelled to stop a
|
||||
// Tauri window drag, which suppresses the mouse events `dblclick` comes from.
|
||||
const DOUBLE_PRESS_MS = 500;
|
||||
const DOUBLE_PRESS_SLOP_PX = 6;
|
||||
let lastPressAt = 0;
|
||||
let lastPressX = 0;
|
||||
|
||||
function isDoublePress(e: PointerEvent): boolean {
|
||||
const paired =
|
||||
// A press that ended in a resize isn't half of a pair.
|
||||
!lastPressResized &&
|
||||
e.timeStamp - lastPressAt < DOUBLE_PRESS_MS &&
|
||||
Math.abs(e.clientX - lastPressX) <= DOUBLE_PRESS_SLOP_PX;
|
||||
// Cleared so a third press starts a new pair instead of fitting again.
|
||||
lastPressAt = paired ? 0 : e.timeStamp;
|
||||
lastPressX = e.clientX;
|
||||
return paired;
|
||||
}
|
||||
const isDoublePress = doublePressDetector();
|
||||
|
||||
function onEdgePointerDown(e: PointerEvent) {
|
||||
// Primary button only; a right-click here would otherwise start a drag.
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
|
||||
if (isDoublePress(e)) {
|
||||
if (isDoublePress(e, lastPressResized)) {
|
||||
void fitSidebarToContent();
|
||||
return;
|
||||
}
|
||||
@@ -223,26 +200,19 @@
|
||||
if (Math.abs(ev.clientX - startX) <= DRAG_SLOP_PX) return;
|
||||
dragged = true;
|
||||
}
|
||||
const width = startWidth + (ev.clientX - startX) / pxPerRem;
|
||||
// One boundary serves both directions: below it the sidebar is collapsed,
|
||||
// above it expanded, so a single drag can cross either way.
|
||||
const collapse = width < MIN_SIDEBAR_WIDTH - COLLAPSE_DRAG_SLACK_REM;
|
||||
if (collapsed.value !== collapse) {
|
||||
collapsed.value = collapse;
|
||||
const drag = dragResult(startWidth, ev.clientX - startX, pxPerRem);
|
||||
if (collapsed.value !== drag.collapse) {
|
||||
collapsed.value = drag.collapse;
|
||||
snapWidth();
|
||||
}
|
||||
if (collapse) {
|
||||
railStretch = Math.max(
|
||||
0,
|
||||
(width - RAIL_WIDTH_REM) * RAIL_STRETCH_FACTOR,
|
||||
);
|
||||
railStretch = drag.railStretch;
|
||||
if (drag.collapse) {
|
||||
// Anything previewed before the threshold was crossed is on the way to
|
||||
// collapsing, not a width to remember.
|
||||
discardSidebarWidthPreview();
|
||||
} else {
|
||||
railStretch = 0;
|
||||
// Off storage; persisted once when the drag ends.
|
||||
previewSidebarWidth(width);
|
||||
previewSidebarWidth(drag.width);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { MIN_SIDEBAR_WIDTH, RAIL_WIDTH_REM } from "@app/lib/sidebar.svelte";
|
||||
|
||||
// How far below the minimum width the drag has to go before it collapses,
|
||||
// rather than the edge just sticking at the minimum.
|
||||
const COLLAPSE_DRAG_SLACK_REM = 2;
|
||||
|
||||
// While collapsed, the rail follows the drag at a fraction of its distance so
|
||||
// it feels attached to the pointer, instead of sitting still until the
|
||||
// threshold and then jumping open.
|
||||
const RAIL_STRETCH_FACTOR = 0.35;
|
||||
|
||||
// Where a drag of the sidebar edge leaves it. One boundary serves both
|
||||
// directions: below it the sidebar is collapsed, above it expanded, so a
|
||||
// single drag can cross either way.
|
||||
export function dragResult(
|
||||
startWidth: number,
|
||||
deltaPx: number,
|
||||
pxPerRem: number,
|
||||
): { collapse: boolean; width: number; railStretch: number } {
|
||||
const width = startWidth + deltaPx / pxPerRem;
|
||||
const collapse = width < MIN_SIDEBAR_WIDTH - COLLAPSE_DRAG_SLACK_REM;
|
||||
const railStretch = collapse
|
||||
? Math.max(0, (width - RAIL_WIDTH_REM) * RAIL_STRETCH_FACTOR)
|
||||
: 0;
|
||||
return { collapse, width, railStretch };
|
||||
}
|
||||
|
||||
const DOUBLE_PRESS_MS = 500;
|
||||
const DOUBLE_PRESS_SLOP_PX = 6;
|
||||
|
||||
// A second press in quick succession, close to the first. A press that ended
|
||||
// in a resize isn't half of a pair, and a pair is cleared so a third press
|
||||
// starts a new one.
|
||||
export function doublePressDetector() {
|
||||
let lastPressAt = -Infinity;
|
||||
let lastPressX = 0;
|
||||
return (
|
||||
press: { timeStamp: number; clientX: number },
|
||||
lastPressResized: boolean,
|
||||
): boolean => {
|
||||
const paired =
|
||||
!lastPressResized &&
|
||||
press.timeStamp - lastPressAt < DOUBLE_PRESS_MS &&
|
||||
Math.abs(press.clientX - lastPressX) <= DOUBLE_PRESS_SLOP_PX;
|
||||
lastPressAt = paired ? -Infinity : press.timeStamp;
|
||||
lastPressX = press.clientX;
|
||||
return paired;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { doublePressDetector, dragResult } from "@app/lib/sidebarResize";
|
||||
|
||||
describe("dragResult", () => {
|
||||
test("widens by the dragged distance", () => {
|
||||
expect(dragResult(16, 32, 16)).toEqual({
|
||||
collapse: false,
|
||||
width: 18,
|
||||
railStretch: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("collapses only past the slack below the minimum width", () => {
|
||||
expect(dragResult(12, -2 * 16, 16).collapse).toBe(false);
|
||||
expect(dragResult(12, -2 * 16 - 1, 16).collapse).toBe(true);
|
||||
});
|
||||
|
||||
test("stretches the rail with a collapsed drag, never below its width", () => {
|
||||
expect(dragResult(5, 2 * 16, 16)).toEqual({
|
||||
collapse: true,
|
||||
width: 7,
|
||||
railStretch: 0.7,
|
||||
});
|
||||
expect(dragResult(5, -16, 16).railStretch).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("doublePressDetector", () => {
|
||||
const press = (timeStamp: number, clientX = 100) => ({ timeStamp, clientX });
|
||||
|
||||
test("pairs two quick presses in the same place", () => {
|
||||
const isDoublePress = doublePressDetector();
|
||||
expect(isDoublePress(press(1000), false)).toBe(false);
|
||||
expect(isDoublePress(press(1499, 106), false)).toBe(true);
|
||||
});
|
||||
|
||||
test("a third press starts a new pair", () => {
|
||||
const isDoublePress = doublePressDetector();
|
||||
isDoublePress(press(1000), false);
|
||||
isDoublePress(press(1100), false);
|
||||
expect(isDoublePress(press(1200), false)).toBe(false);
|
||||
expect(isDoublePress(press(1300), false)).toBe(true);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["too late", press(1500), false],
|
||||
["too far", press(1100, 107), false],
|
||||
["after a resize", press(1100), true],
|
||||
])("doesn't pair a press %s", (_, second, resized) => {
|
||||
const isDoublePress = doublePressDetector();
|
||||
isDoublePress(press(1000), false);
|
||||
expect(isDoublePress(second, resized)).toBe(false);
|
||||
});
|
||||
|
||||
test("the first press right after launch isn't a pair", () => {
|
||||
expect(doublePressDetector()(press(10, 0), false)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user