fix(collections): make per-card remove buttons usable on touch #19
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1517,7 +1517,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.88.0"
|
version = "0.88.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.88.0"
|
version = "0.88.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.88.0",
|
"version": "0.88.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -406,6 +406,19 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Touch has no hover, so the reveal-on-hover treatment leaves the
|
||||||
|
remove buttons invisible and undiscoverable. Show them persistently
|
||||||
|
and enlarge the hit area on phones. (Capped well below 44px because
|
||||||
|
the button floats over a 4-up cover thumbnail — a full touch-floor
|
||||||
|
square would swallow the cover.) */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.remove {
|
||||||
|
opacity: 1;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
49
frontend/src/routes/collections/[id]/remove-touch.test.ts
Normal file
49
frontend/src/routes/collections/[id]/remove-touch.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
// The per-card "remove" buttons on a collection are revealed on hover /
|
||||||
|
// focus-within (opacity 0 → 1), which leaves them invisible and
|
||||||
|
// undiscoverable on touch. They must be shown persistently at the mobile
|
||||||
|
// breakpoint. jsdom can't evaluate @media or layout, so this pins the CSS
|
||||||
|
// contract: the 640px block makes `.remove` opaque. (Same approach as the
|
||||||
|
// shared touch-target contract test.)
|
||||||
|
|
||||||
|
const SRC = readFileSync(
|
||||||
|
resolve(process.cwd(), 'src/routes/collections/[id]/+page.svelte'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Brace-balanced extraction of every `@media (max-width: 640px) { ... }` body.
|
||||||
|
function mobileBlocks(css: string): string {
|
||||||
|
const marker = '@media (max-width: 640px)';
|
||||||
|
let out = '';
|
||||||
|
let from = 0;
|
||||||
|
for (;;) {
|
||||||
|
const start = css.indexOf(marker, from);
|
||||||
|
if (start === -1) break;
|
||||||
|
const open = css.indexOf('{', start);
|
||||||
|
if (open === -1) break;
|
||||||
|
let depth = 0;
|
||||||
|
let i = open;
|
||||||
|
for (; i < css.length; i++) {
|
||||||
|
if (css[i] === '{') depth++;
|
||||||
|
else if (css[i] === '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth === 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += css.slice(open + 1, i) + '\n';
|
||||||
|
from = i + 1;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const squish = (s: string) => s.replace(/\s+/g, ' ');
|
||||||
|
|
||||||
|
describe('collection remove buttons on touch', () => {
|
||||||
|
it('reveals the remove buttons persistently at the mobile breakpoint', () => {
|
||||||
|
const mobile = squish(mobileBlocks(SRC));
|
||||||
|
expect(mobile).toMatch(/\.remove[^}]*opacity:\s*1/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user