mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
Click off to deselect
This commit is contained in:
@@ -86,6 +86,10 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.laneHint {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.laneEmpty {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -116,6 +116,8 @@ export default function PageTracks() {
|
||||
[deletePages, selection.selectedIds],
|
||||
);
|
||||
|
||||
const clearSelection = selection.clear;
|
||||
|
||||
// ── Drag and drop ────────────────────────────────────────────────────────
|
||||
|
||||
const sensors = useSensors(
|
||||
@@ -331,7 +333,13 @@ export default function PageTracks() {
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<div className={styles.scroller} data-scrolling-container="true">
|
||||
<div
|
||||
className={styles.scroller}
|
||||
data-scrolling-container="true"
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) clearSelection();
|
||||
}}
|
||||
>
|
||||
{workspace.order.map((fileId) => {
|
||||
const track = workspace.tracks[fileId];
|
||||
if (!track) return null;
|
||||
@@ -351,6 +359,7 @@ export default function PageTracks() {
|
||||
thumbnails={thumbnails}
|
||||
onSelectPage={selection.selectPage}
|
||||
onSelectTrack={selection.selectTrack}
|
||||
onClearSelection={clearSelection}
|
||||
onRotate={rotatePages}
|
||||
onDelete={deletePages}
|
||||
/>
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface TrackRowProps {
|
||||
modifiers: PageClickModifiers,
|
||||
) => void;
|
||||
onSelectTrack: (fileId: FileId) => void;
|
||||
/** Called when the click landed on empty lane surface, not on a page. */
|
||||
onClearSelection: () => void;
|
||||
onRotate: (pageIds: string[], delta: number) => void;
|
||||
onDelete: (pageIds: string[]) => void;
|
||||
}
|
||||
@@ -57,6 +59,7 @@ function TrackRowImpl({
|
||||
thumbnails,
|
||||
onSelectPage,
|
||||
onSelectTrack,
|
||||
onClearSelection,
|
||||
onRotate,
|
||||
onDelete,
|
||||
}: TrackRowProps) {
|
||||
@@ -168,15 +171,22 @@ function TrackRowImpl({
|
||||
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
data-track-lane={track.fileId}
|
||||
className={[
|
||||
styles.lane,
|
||||
track.pages.length === 0 ? styles.laneEmpty : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
// Only a click on the lane itself, never one that bubbled up from a
|
||||
// page: clicking a tile would otherwise select it and immediately
|
||||
// clear it again.
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) onClearSelection();
|
||||
}}
|
||||
>
|
||||
{track.pages.length === 0 && (
|
||||
<span>
|
||||
<span className={styles.laneHint}>
|
||||
{t(
|
||||
"pageTracks.emptyTrack",
|
||||
"No pages left. Drag pages here, or save to close this file.",
|
||||
|
||||
@@ -360,4 +360,59 @@ test.describe("Page Editor tracks", () => {
|
||||
// Only the two clicked pages turn; the ones in between are untouched.
|
||||
expect(await readRotations(rotated, 4)).toEqual([90, 90, 0, 180]);
|
||||
});
|
||||
|
||||
test("clicking the empty space around the pages deselects everything", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openPageEditor(page);
|
||||
const rotated = track(page, "rotated-pages.pdf");
|
||||
const tiles = rotated.locator("[data-page-id]");
|
||||
await expect(tiles).toHaveCount(4, { timeout: 30_000 });
|
||||
const anySelected = page.locator('[data-page-id][data-selected="true"]');
|
||||
|
||||
await tiles.nth(0).click();
|
||||
await tiles.nth(2).click();
|
||||
await expect(anySelected).toHaveCount(2);
|
||||
|
||||
// The lane runs past its last page; that surface is not a page.
|
||||
const box = await rotated.locator("[data-track-lane]").boundingBox();
|
||||
if (!box) throw new Error("lane is not laid out");
|
||||
const last = await tiles.nth(3).boundingBox();
|
||||
if (!last) throw new Error("tile is not laid out");
|
||||
await page.mouse.click(
|
||||
(last.x + last.width + box.x + box.width) / 2,
|
||||
box.y + box.height / 2,
|
||||
);
|
||||
await expect(anySelected).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a drag that lands on empty lane space keeps the moved selection", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openPageEditor(page);
|
||||
const rotated = track(page, "rotated-pages.pdf");
|
||||
const sample = track(page, "sample.pdf");
|
||||
const tiles = rotated.locator("[data-page-id]");
|
||||
await expect(tiles).toHaveCount(4, { timeout: 30_000 });
|
||||
|
||||
await tiles.nth(0).click();
|
||||
await tiles.nth(1).click();
|
||||
await expect(
|
||||
page.locator('[data-page-id][data-selected="true"]'),
|
||||
).toHaveCount(2);
|
||||
|
||||
// sample.pdf has one page, so the lane past it is empty space: the drop
|
||||
// releases over the lane, which is also where a deselect click would land.
|
||||
await dragPageOnto(
|
||||
page,
|
||||
tiles.nth(0),
|
||||
sample.locator("[data-page-id]").first(),
|
||||
0.9,
|
||||
);
|
||||
|
||||
await expect(sample.locator("[data-page-id]")).toHaveCount(3);
|
||||
await expect(
|
||||
page.locator('[data-page-id][data-selected="true"]'),
|
||||
).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user