()
| 3508 | // --- Zoom (wheel) + pan (drag) + drag existing AP nodes / walls --- |
| 3509 | if (cv) { |
| 3510 | const norm = (e) => { |
| 3511 | const r = cv.getBoundingClientRect(); |
| 3512 | const px = e.clientX - r.left, py = e.clientY - r.top; |
| 3513 | const vp = _wifiHmViewport(cv.clientWidth, cv.clientHeight || cv.clientWidth); |
| 3514 | return { px, py, wx: vp.fromX(px), wy: vp.fromY(py), vp }; |
| 3515 | }; |
| 3516 | cv.addEventListener('wheel', (e) => { |
| 3517 | e.preventDefault(); |
| 3518 | const r = cv.getBoundingClientRect(); |
| 3519 | wifiHmZoom(e.deltaY < 0 ? 1.25 : 0.8, e.clientX - r.left, e.clientY - r.top); |
| 3520 | }, { passive: false }); |
| 3521 | cv.addEventListener('pointerdown', (e) => { |
| 3522 | _wifiHm.suppressClick = false; // fresh press — never carry a stale flag |
| 3523 | const p = norm(e); |
| 3524 | // Design objects win the press (but not the 2nd click of a wall). |
| 3525 | if (_wifiHm.mode === 'design' && !_wifiHm.wallStart) { |
| 3526 | const hit = _wifiHmHitTest(p); |
| 3527 | if (hit) { |
| 3528 | _wifiHm.drag = hit; _wifiHm.dragMoved = false; |
| 3529 | try { cv.setPointerCapture(e.pointerId); } catch (_) {} |
| 3530 | cv.style.cursor = hit.kind === 'column-resize' ? 'nwse-resize' : 'grabbing'; |
| 3531 | e.preventDefault(); |
| 3532 | return; |
| 3533 | } |
| 3534 | } |
| 3535 | // Empty area: a drag pans the zoomed view. At 100% there is nothing |
| 3536 | // to pan, so leave the press alone (it stays a plain sample click). |
| 3537 | if (_wifiHm.view.zoom > 1 || e.shiftKey || e.button === 1) { |
| 3538 | _wifiHm.pan = { sx: e.clientX, sy: e.clientY, |
| 3539 | cx: _wifiHm.view.cx, cy: _wifiHm.view.cy, active: false }; |
| 3540 | try { cv.setPointerCapture(e.pointerId); } catch (_) {} |
| 3541 | } |
| 3542 | }); |
| 3543 | cv.addEventListener('pointermove', (e) => { |
| 3544 | if (_wifiHm.pan) { |
| 3545 | const dx = e.clientX - _wifiHm.pan.sx, dy = e.clientY - _wifiHm.pan.sy; |
| 3546 | if (!_wifiHm.pan.active && Math.hypot(dx, dy) > 4) _wifiHm.pan.active = true; |
| 3547 | if (_wifiHm.pan.active) { |
| 3548 | const vp = _wifiHmViewport(cv.clientWidth, cv.clientHeight || cv.clientWidth); |
| 3549 | _wifiHm.view.cx = _wifiHm.pan.cx - dx / vp.pw * vp.span; |
| 3550 | _wifiHm.view.cy = _wifiHm.pan.cy - dy / vp.ph * vp.span; |
| 3551 | _wifiHmClampView(); |
| 3552 | cv.style.cursor = 'grabbing'; |
| 3553 | _wifiHmDragRender(); |
| 3554 | } |
| 3555 | return; |
| 3556 | } |
| 3557 | const p = norm(e); |
| 3558 | if (!_wifiHm.drag) { |
| 3559 | if (_wifiHm.mode === 'design' && !_wifiHm.wallStart) { |
| 3560 | const hit = _wifiHmHitTest(p); |
no test coverage detected