Text layers: click already-selected text to enter edit mode

The previous click flow only entered edit mode on a true double-click
(event.detail > 1 on the second pointerdown), which is timing-
sensitive — slow second clicks fell through to layer-drag and edit
mode was hard to reach. Result: users clicked the text and the
whole layer kept "selecting" as an object.

Three changes to startLayerDrag to fix it:

1. Capture `wasSoleSelected = isLayerSelected(layer.id) && selectedLayerIds().length === 1`
   at the top of the handler, BEFORE the selection logic runs.
   This tells us whether the user was clicking on a text layer
   that was already the lone selection — the standard signal in
   PowerPoint / Keynote / Google Slides that the user wants to
   edit the text now.

2. A small dead-zone in the move handler (Math.hypot(dx, dy) < 4
   ignores tiny jitter). Without it, a click with a 1-pixel mouse
   wobble flipped `moved` to true and skipped the edit-mode path.

3. New branch in stop() — if the pointer was released without
   crossing the dead-zone (moved === false), the click target was
   inside .textLayer, and wasSoleSelected was true, focus the
   contenteditable and place the caret at the click position via
   document.caretRangeFromPoint (with a caretPositionFromPoint
   fallback for Firefox-style browsers).

Net behaviour:

- First click on an unselected text layer → selects (drag handles
  appear, no edit mode).
- Second click on the same now-selected text layer → enters edit
  mode, caret lands where the click was.
- Click + drag on a selected text layer → drags normally (moved
  > 4px, edit-mode branch skipped).
- Existing double-click path (event.detail > 1) still works as a
  belt-and-suspenders fallback for very fast double-clicks.

CDP synthesized-event tests couldn't fully drive the pointer-
capture path so end-to-end verification needs a real browser; the
logic was code-reviewed.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.7 committed 2026-05-27 08:50:17 -04:00
1 parent aaf044a946
commit df45b63f45
1 file changed
+39 -3
+39 -3
View File
@@ -13993,6 +13993,11 @@
if (layer.locked) { setLayerSelection([layer.id], layer.id); render(); return; }
const textTarget = event.target.closest('.textLayer');
if (!textTarget) blurEditableFocus();
// Capture BEFORE we touch selection state: was this text layer
// already the lone-selected layer when the user clicked it? If so,
// a click-without-drag should drop the user into edit mode (the
// standard slide-editor UX). Stashed for the pointerup branch below.
const wasSoleSelected = isLayerSelected(layer.id) && selectedLayerIds().length === 1;
const additiveSelect = !!(event.shiftKey || event.ctrlKey || event.metaKey);
if (additiveSelect) {
event.preventDefault();
@@ -14029,9 +14034,17 @@
el.setPointerCapture(event.pointerId);
const move = (moveEvent) => {
// Small dead-zone so a click with a tiny pixel jitter isn't
// interpreted as a drag. Anything inside this radius is ignored;
// the user can still enter text-edit mode via the pointerup branch.
if (!moved) {
const ddx = moveEvent.clientX - startX;
const ddy = moveEvent.clientY - startY;
if (Math.hypot(ddx, ddy) < 4) return;
}
if (!snapshotted) {
// Capture the pre-drag state on the first pointermove so a click
// without movement doesn't pollute the undo stack.
// Capture the pre-drag state on the first real pointermove so a
// click without movement doesn't pollute the undo stack.
pushHistory(dragLayers.length > 1 ? 'move selected layers' : 'move layer');
snapshotted = true;
}
@@ -14054,7 +14067,7 @@
syncLayerInputs();
};
const stop = () => {
const stop = (upEvent) => {
document.body.classList.remove('dragging-layer');
el.removeEventListener('pointermove', move);
el.removeEventListener('pointerup', stop);
@@ -14062,6 +14075,29 @@
if (moved) {
el.dataset.dragMoved = '1';
setTimeout(() => { delete el.dataset.dragMoved; }, 0);
} else if (textTarget && wasSoleSelected) {
// Click without drag on a text layer that was already the lone
// selection → drop into edit mode. Focus the contenteditable and
// place the caret at the click position so the user can start
// selecting / typing immediately. This is the standard "select
// once, click again to edit" pattern from PowerPoint / Keynote.
textTarget.focus();
const cx = (upEvent && typeof upEvent.clientX === 'number') ? upEvent.clientX : startX;
const cy = (upEvent && typeof upEvent.clientY === 'number') ? upEvent.clientY : startY;
try {
let range = null;
if (typeof document.caretRangeFromPoint === 'function') {
range = document.caretRangeFromPoint(cx, cy);
} else if (typeof document.caretPositionFromPoint === 'function') {
const pos = document.caretPositionFromPoint(cx, cy);
if (pos) { range = document.createRange(); range.setStart(pos.offsetNode, pos.offset); range.collapse(true); }
}
if (range) {
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} catch (e) {}
}
localStorage.setItem('case-cover-template-maker-autosave', JSON.stringify(state));
};