fix: address Greptile review and fix CI test mock for touch handlers

Three fixes in one commit:

1. **CI test mock** — Added addEventListener/removeEventListener to the
   Node.js makeElement mock so viewport.addEventListener() calls in the
   touch handler registration don't crash with 'not a function'.

2. **Two-finger pan during pinch** — Replaced _setScale() call with
   direct transform math. The new formula computes scale and position
   simultaneously:
     ratio = boundedScale / pinchStartScale
     x = currMidX - (startMidX - startPosX) * ratio
   This keeps the diagram under both fingers regardless of midpoint
   drift, enabling simultaneous zoom + pan.

3. **Post-pinch click suppression** — _onTouchEnd now sets dragged=true
   when exiting pinch mode so any synthesized click event is caught
   by the existing dragged guard in _openViewerOnClick instead of
   opening the lightbox.
This commit is contained in:
silent-reader-cn authored and nesquena-hermes committed 2026-07-15 23:02:13 +00:00
1 parent 7419b1bb51
commit 0403f00789
2 files changed
+29 -4

No files matched your search

+20 -4
View File
@@ -2016,6 +2016,10 @@ function _mountMermaidViewer(svgEl, options = {}) {
pinching: false,
pinchStartDist: 0,
pinchStartScale: 1,
pinchStartCX: 0,
pinchStartCY: 0,
pinchStartX: 0,
pinchStartY: 0,
};
root._mermaidViewer = state;
@@ -2219,6 +2223,11 @@ function _mountMermaidViewer(svgEl, options = {}) {
state.pinching = true;
state.pinchStartDist = _touchDist(e.touches);
state.pinchStartScale = state.scale;
state.pinchStartX = state.x;
state.pinchStartY = state.y;
const rect = viewport.getBoundingClientRect();
state.pinchStartCX = (e.touches[0].clientX + e.touches[1].clientX) / 2 - (rect.left || 0);
state.pinchStartCY = (e.touches[0].clientY + e.touches[1].clientY) / 2 - (rect.top || 0);
_endPointerDrag();
if(e.preventDefault) e.preventDefault();
}
@@ -2226,19 +2235,26 @@ function _mountMermaidViewer(svgEl, options = {}) {
function _onTouchMove(e){
if(!state.pinching || e.touches.length < 2) return;
const currDist = _touchDist(e.touches);
if(state.pinchStartDist > 0){
const rect = viewport.getBoundingClientRect();
const cx = (e.touches[0].clientX + e.touches[1].clientX) / 2 - (rect.left || 0);
const cy = (e.touches[0].clientY + e.touches[1].clientY) / 2 - (rect.top || 0);
_setScale(state.pinchStartScale * (currDist / state.pinchStartDist), cx, cy);
const currDist = _touchDist(e.touches);
if(state.pinchStartDist > 0 && state.pinchStartScale > 0){
const rawScale = state.pinchStartScale * (currDist / state.pinchStartDist);
const boundedScale = Math.max(_minScale(), Math.min(_MERMAID_VIEWER_MAX_SCALE, rawScale));
const ratio = boundedScale / state.pinchStartScale;
state.scale = boundedScale;
state.x = cx - (state.pinchStartCX - state.pinchStartX) * ratio;
state.y = cy - (state.pinchStartCY - state.pinchStartY) * ratio;
_applyTransform();
}
if(e.preventDefault) e.preventDefault();
}
function _onTouchEnd(e){
if(e.touches.length < 2){
if(e.touches.length < 2 && state.pinching){
state.pinching = false;
state.dragged = true;
}
}
+9
View File
@@ -113,6 +113,15 @@ function makeElement(tagName) {
this.releasedPointerId = pointerId;
if (this.capturedPointerId === pointerId) this.capturedPointerId = null;
},
_listeners: {},
addEventListener(type, handler, _options) {
(this._listeners[type] ||= []).push(handler);
},
removeEventListener(type, handler) {
const list = this._listeners[type] || [];
const idx = list.indexOf(handler);
if (idx >= 0) list.splice(idx, 1);
},
cloneNode() {
const copy = makeElement(this.tagName);
copy.className = this.className;