Use a plus-circle button for adding pages #10

Merged
reudy merged 1 commit from add-page-plus into main 2026-09-27 22:52:57 +02:00

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { onMount, untrack } from 'svelte';
import CirclePlusIcon from '@lucide/svelte/icons/circle-plus';
import DownloadIcon from '@lucide/svelte/icons/download';
import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
@ -117,15 +118,18 @@
return rectBeside(a, slot.dir, s);
}
/** Hit area of the add-page bar: a strip hugging the anchor's free edge. */
function barRect(slot: Slot): Rect | null {
/** The add-page button: a 24 px circle centred on the anchor's free edge, 6 px out. */
function plusRect(slot: Slot): Rect | null {
const a = doc.rectOf(slot.anchor);
if (!a) return null;
const t = 20 / viewport.scale;
if (slot.dir === 'right') return { x: a.x + a.width, y: a.y, width: t, height: a.height };
if (slot.dir === 'left') return { x: a.x - t, y: a.y, width: t, height: a.height };
if (slot.dir === 'down') return { x: a.x, y: a.y + a.height, width: a.width, height: t };
return { x: a.x, y: a.y - t, width: a.width, height: t };
const d = 24 / viewport.scale;
const gap = 6 / viewport.scale;
const cx = a.x + (a.width - d) / 2;
const cy = a.y + (a.height - d) / 2;
if (slot.dir === 'right') return { x: a.x + a.width + gap, y: cy, width: d, height: d };
if (slot.dir === 'left') return { x: a.x - gap - d, y: cy, width: d, height: d };
if (slot.dir === 'down') return { x: cx, y: a.y + a.height + gap, width: d, height: d };
return { x: cx, y: a.y - gap - d, width: d, height: d };
}
/** Run a tree change without the anchor page jumping on screen. */
@ -291,10 +295,10 @@
// Fingers ink only when enabled and no pen has been used; otherwise they pan/zoom.
const fingerInks = tools.inking && settings.data.fingerDraw && !palm.penSeen;
// Taps on an add-page bar get the same palm policy as writing.
const bar = target.closest<HTMLElement>('.ghost');
// Taps on an add-page button get the same palm policy as writing.
const plus = target.closest<HTMLElement>('.ghost');
if (e.pointerType === 'touch') {
const decision = palm.evaluate(e, fingerInks || bar ? 'draw' : 'navigate');
const decision = palm.evaluate(e, fingerInks || plus ? 'draw' : 'navigate');
if (!decision.accept) return; // palm: ignore entirely
touches.set(e.pointerId, p);
if (touches.size >= 2) {
@ -305,9 +309,9 @@
}
}
if (bar && e.button === 0) {
if (plus && e.button === 0) {
e.preventDefault();
const slot = { anchor: bar.dataset.anchor!, dir: bar.dataset.dir as Dir };
const slot = { anchor: plus.dataset.anchor!, dir: plus.dataset.dir as Dir };
gesture = { kind: 'add', pointerId: e.pointerId, pointerType: e.pointerType, startX: p.x, startY: p.y, slot };
capture(e.pointerId);
return;
@ -397,7 +401,7 @@
}
if (gesture.kind === 'add') {
// Dragging off the bar is not a tap.
// Dragging off the button is not a tap.
if (Math.hypot(p.x - gesture.startX, p.y - gesture.startY) > 12) gesture = null;
return;
}
@ -593,7 +597,7 @@
{/each}
{#each ghostSlots as slot (slot.anchor + slot.dir)}
{@const r = barRect(slot)}
{@const r = plusRect(slot)}
{#if r}
<!-- Pointer taps go through the palm-checked gesture; onclick only serves the keyboard. -->
<button
@ -603,7 +607,9 @@
style="left:{r.x}px; top:{r.y}px; width:{r.width}px; height:{r.height}px"
title="Add page ({slot.dir})"
onclick={(e) => e.detail === 0 && addPage(slot.anchor, slot.dir)}
></button>
>
<CirclePlusIcon size="100%" strokeWidth={1.5} />
</button>
{/if}
{/each}
@ -726,46 +732,18 @@
}
.ghost {
position: absolute;
background: none;
display: grid;
background: var(--bg-canvas);
border: 0;
border-radius: 50%;
padding: 0;
color: var(--faint);
cursor: pointer;
transition: color 0.12s;
}
/* The visible bar: 4 screen px thick, 6 px off the page edge. */
.ghost::before {
content: '';
position: absolute;
background: var(--ghost-border);
border-radius: calc(2px / var(--s));
transition: background 0.12s;
}
.ghost[data-dir='left']::before,
.ghost[data-dir='right']::before {
top: 0;
bottom: 0;
width: calc(4px / var(--s));
}
.ghost[data-dir='up']::before,
.ghost[data-dir='down']::before {
left: 0;
right: 0;
height: calc(4px / var(--s));
}
.ghost[data-dir='right']::before {
left: calc(6px / var(--s));
}
.ghost[data-dir='left']::before {
right: calc(6px / var(--s));
}
.ghost[data-dir='down']::before {
top: calc(6px / var(--s));
}
.ghost[data-dir='up']::before {
bottom: calc(6px / var(--s));
}
.ghost:hover::before,
.ghost:focus-visible::before {
background: var(--accent);
.ghost:hover,
.ghost:focus-visible {
color: var(--accent);
}
.slot {
position: absolute;