feat(web): adjust handles float above the text with tips marking the boundary

User report: the handles covered the highlighted text. The dots now
hang ABOVE the extent's boundary corners (teardrop tip down at the
first rect's top-left / last rect's top-right), so the highlighted
line stays readable.

Same treatment as the app leg: the nearest-char scan in the endpoint
drag is band-constrained to the boundary rect's own line (the lifted
finger floats ABOVE the text — a whole-table scan re-anchored onto the
line above). Cross-line changes go through re-selection as before.
This commit is contained in:
John O'Keefe committed 2026-10-11 17:25:58 -04:00
1 parent 1c209408cc
commit a28ae5c88d
1 file changed
+25 -9
+25 -9
View File
@@ -1747,11 +1747,13 @@ document.addEventListener("alpine:init", () => {
const first = ctx.rects[0];
const last = ctx.rects[ctx.rects.length - 1];
if (!first || !last) return;
// Start dot at the first rect's LEFT-MIDDLE, end dot at the last
// rect's RIGHT-MIDDLE — the range boundary points.
// Start dot floating ABOVE the first rect's top-left corner, end
// dot above the last rect's top-right corner — the teardrop tip
// points down at the boundary so the highlighted text underneath
// stays readable (user request: the handle covered the text).
const pts: Array<{ which: "start" | "end"; fx: number; fy: number }> = [
{ which: "start", fx: first[0], fy: first[1] + first[3] / 2 },
{ which: "end", fx: last[0] + last[2], fy: last[1] + last[3] / 2 },
{ which: "start", fx: first[0], fy: first[1] },
{ which: "end", fx: last[0] + last[2], fy: last[1] },
];
for (const pt of pts) {
let dot = layer.querySelector<HTMLDivElement>(
@@ -1790,8 +1792,10 @@ document.addEventListener("alpine:init", () => {
layer.appendChild(dot);
}
const host = map.toHost(pt.fx, pt.fy);
// The teardrop's rotated tip is its bottom point: hang the box
// ABOVE the boundary corner.
dot.style.left = `${host.x - 13}px`;
dot.style.top = `${host.y - 13}px`;
dot.style.top = `${host.y - 22}px`;
}
},
/** Line boxes (page fractions) of a contiguous char-table span:
@@ -1846,15 +1850,27 @@ document.addEventListener("alpine:init", () => {
if (!ctx) return;
const map = this.pdfPageMapping(ctx.index);
if (!map) return;
const { fx, fy } = map.toFraction(clientX, clientY);
const { fx } = map.toFraction(clientX, clientY);
const chars = ctx.chars;
if (!chars.length) return;
// The boundary stays on its OWN line: candidates are the chars in
// the boundary rect's line band, and the finger's X picks the
// glyph (vertical finger movement is ignored — cross-line changes
// go through re-selection). A whole-table Euclidean scan here
// re-anchored onto the line ABOVE when the finger floated above
// the text (lifted handles).
const boundary =
which === "start" ? ctx.rects[0] : ctx.rects[ctx.rects.length - 1];
const bandTop = boundary[1] - 0.002;
const bandBottom = boundary[1] + boundary[3] + 0.002;
let best = 0;
let bestD = Infinity;
for (let i = 0; i < chars.length; i++) {
const cx = (chars[i].x0 + chars[i].x1) / 2;
const cy = (chars[i].y0 + chars[i].y1) / 2;
const d = (cx - fx) * (cx - fx) + (cy - fy) * (cy - fy);
const c = chars[i];
const cy = (c.y0 + c.y1) / 2;
if (cy < bandTop || cy > bandBottom) continue;
const cx = (c.x0 + c.x1) / 2;
const d = (cx - fx) * (cx - fx);
if (d < bestD) {
bestD = d;
best = i;