diff --git a/bundled-addons/pdf-editor/editor.css b/bundled-addons/pdf-editor/editor.css index a6bb2d9c..8e65d2fb 100644 --- a/bundled-addons/pdf-editor/editor.css +++ b/bundled-addons/pdf-editor/editor.css @@ -260,10 +260,12 @@ body[data-dragging="1"] .selbar, .btn.styl.active { background: rgb(from var(--acid) r g b / .16); border-color: rgb(from var(--acid) r g b / .7); color: var(--acid); } -/* Resize handles. Only grabbable with the select tool armed, so a drawing - drag near a selected mark is never stolen by one. */ -body[data-mode="select"] .smOverlay .handle { pointer-events: all; } -.smOverlay .handle { pointer-events: none; } +/* Resize handles stay grabbable whatever tool is armed, so a shape can be + sized the moment it is drawn without a detour through the select tool. + Safe to leave live because a handle is a 9 px square that exists only while + something is selected — aiming at one is not something you do by accident, + and Escape clears the selection if you want the space back for drawing. */ +.smOverlay .handle { pointer-events: all; } /* The invisible grab area under a thin stroke. `stroke` rather than `visibleStroke`: the copy is transparent on purpose. */ diff --git a/bundled-addons/pdf-editor/lib/tools.js b/bundled-addons/pdf-editor/lib/tools.js index f78221fd..5125d4c0 100644 --- a/bundled-addons/pdf-editor/lib/tools.js +++ b/bundled-addons/pdf-editor/lib/tools.js @@ -135,26 +135,31 @@ export class Tools { onDown(e) { if (e.button !== 0) return; - // A resize handle is checked before anything else: it sits outside the - // mark's own bounds, so the page under it may not even be the page the - // mark is on. - if (this.tool === "select") { - const handle = e.target?.closest?.(".handle")?.dataset?.handle; - if (handle && this.selected) { - const a = this.model.annot(this.selected); - const ov = a && this.strip.overlayFor(a.page); - if (a && ov?.viewport) { - const view = this.strip.views.get(a.page); - const r = view.pv.div.getBoundingClientRect(); - const [hx, hy] = ov.toPdf(e.clientX - r.left, e.clientY - r.top); - this.drag = { - kind: "resize", uid: a.page, id: a.id, handle, - x0: hx, y0: hy, orig: structuredClone(a), from: boundsOf(a), - cx: e.clientX, cy: e.clientY, moved: false, - }; - e.preventDefault(); - return; - } + // A resize handle is checked before anything else, and whatever tool is + // armed. Two reasons it comes first: it sits outside the mark's own + // bounds, so the page under it may not even be the page the mark is on; + // and a shape is selected the instant it is drawn, so grabbing a corner + // has to work right then, without a detour through the select tool to + // resize the thing you are still looking at. + // + // It cannot be confused with drawing. A handle is a 9 px square that + // exists only while something is selected; nobody aims at one by + // accident, and Escape drops the selection if the space is wanted back. + const handle = e.target?.closest?.(".handle")?.dataset?.handle; + if (handle && this.selected) { + const a = this.model.annot(this.selected); + const ov = a && this.strip.overlayFor(a.page); + if (a && ov?.viewport) { + const view = this.strip.views.get(a.page); + const r = view.pv.div.getBoundingClientRect(); + const [hx, hy] = ov.toPdf(e.clientX - r.left, e.clientY - r.top); + this.drag = { + kind: "resize", uid: a.page, id: a.id, handle, + x0: hx, y0: hy, orig: structuredClone(a), from: boundsOf(a), + cx: e.clientX, cy: e.clientY, moved: false, + }; + e.preventDefault(); + return; } } @@ -255,8 +260,16 @@ export class Tools { } onUp(e) { - if (TEXT_MARKUP.has(this.tool)) { this._commitTextMarkup(); return; } - const d = this.drag; + // A handle drag is finished here whatever tool is armed. Without this the + // text-markup tools would return on the line below and strand a resize + // half-done: applied to the live mark, never journalled, and with + // this.drag still set so the next press behaves oddly. + const inFlight = this.drag; + if (!(inFlight && inFlight.kind === "resize") && TEXT_MARKUP.has(this.tool)) { + this._commitTextMarkup(); + return; + } + const d = inFlight; this.drag = null; if (!d) return; this.strip.setPreview(null, null);