mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
editor: gestures work on touch — pinch zooms, a moving finger pans
The stage pointerdown bailed out whenever _markup was set, so in the plan editor no pointer was ever tracked: no pinch, no pan — on a phone the plan could not be zoomed or moved at all (owner's report). But drawing is CLICK-based, so the two coexist: a finger that moves pans (and suppresses the synthesized click so the release feeds no tool), two fingers pinch, a clean tap still draws. Pointers that start on labels, handles, markers or buttons stay out — those run their own drags. The tool preview keeps following the tracked finger. New smoke: smoke_editor_gestures (pinch in plan mode, pan without drawing, tap still draws). Inventory: 140 / 51 / 43 / 66.
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
// На телефоне в редакторах не работали зум и навигация жестами: pointerdown
|
||||||
|
// сцены выходил сразу при _markup, так что пинч и пан не начинались вовсе.
|
||||||
|
// Рисование кликается, жесты двигаются — они совместимы; палец с движением
|
||||||
|
// панорамирует, два пальца зумируют, отпускание после жеста не рисует точку.
|
||||||
|
import { launch, checkAll, finish } from './serve.mjs';
|
||||||
|
const { page, browser } = await launch();
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
const pd = (c, id, x, y) => c._stagePointerDown({ pointerId: id, clientX: x, clientY: y, target: c._stageEl, preventDefault() {} });
|
||||||
|
const pm = (c, id, x, y) => c._stagePointerMove({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
const pu = (c, id, x, y) => c._stagePointerUp({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
|
||||||
|
// --- пинч-зум в редакторе плана -----------------------------------------
|
||||||
|
out.pinchZoomsInPlanEditor = await page.evaluate(() => {
|
||||||
|
const c = window.__card;
|
||||||
|
c._setMode('plan');
|
||||||
|
const pd = (id, x, y) => c._stagePointerDown({ pointerId: id, clientX: x, clientY: y, target: c._stageEl, preventDefault() {} });
|
||||||
|
const pm = (id, x, y) => c._stagePointerMove({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
const pu = (id, x, y) => c._stagePointerUp({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
c._resetZoom();
|
||||||
|
const z0 = c._zoom;
|
||||||
|
pd(1, 300, 300); pd(2, 400, 300); // два пальца
|
||||||
|
pm(1, 250, 300); pm(2, 450, 300); // разводим
|
||||||
|
const zoomed = c._zoom > z0 * 1.5;
|
||||||
|
pu(1, 250, 300); pu(2, 450, 300);
|
||||||
|
return zoomed && c._path.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- пан одним пальцем в редакторе, точка не рисуется --------------------
|
||||||
|
out.panWorksAndDoesNotDraw = await page.evaluate(async () => {
|
||||||
|
const c = window.__card;
|
||||||
|
const pd = (id, x, y) => c._stagePointerDown({ pointerId: id, clientX: x, clientY: y, target: c._stageEl, preventDefault() {} });
|
||||||
|
const pm = (id, x, y) => c._stagePointerMove({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
const pu = (id, x, y) => c._stagePointerUp({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
const st = c._stageEl;
|
||||||
|
c._zoomAt(st.clientWidth / 2, st.clientHeight / 2, 3); // есть куда панорамировать
|
||||||
|
const v0 = { ...c._view };
|
||||||
|
pd(3, 300, 300);
|
||||||
|
pm(3, 380, 340); pm(3, 420, 360);
|
||||||
|
const panned = Math.abs(c._view.x - v0.x) > 1 || Math.abs(c._view.y - v0.y) > 1;
|
||||||
|
const suppressed = c._suppressClick === true;
|
||||||
|
c._markupClick({ composedPath: () => [], clientX: 420, clientY: 360 }); // синтезированный click после пана
|
||||||
|
const noDot = c._path.length === 0;
|
||||||
|
pu(3, 420, 360);
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
return panned && suppressed && noDot;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- обычный клик без движения по-прежнему рисует ------------------------
|
||||||
|
out.tapStillDraws = await page.evaluate(() => {
|
||||||
|
const c = window.__card;
|
||||||
|
const pd = (id, x, y) => c._stagePointerDown({ pointerId: id, clientX: x, clientY: y, target: c._stageEl, preventDefault() {} });
|
||||||
|
const pu = (id, x, y) => c._stagePointerUp({ pointerId: id, clientX: x, clientY: y });
|
||||||
|
c._resetZoom();
|
||||||
|
c._tool = 'draw';
|
||||||
|
const before = c._path.length;
|
||||||
|
pd(4, 300, 300); pu(4, 300, 300);
|
||||||
|
const st = c._stageEl.getBoundingClientRect();
|
||||||
|
c._markupClick({ composedPath: () => [], clientX: st.left + 200, clientY: st.top + 200 });
|
||||||
|
const drew = c._path.length > before;
|
||||||
|
c._path = []; c._setMode('view');
|
||||||
|
return drew;
|
||||||
|
});
|
||||||
|
|
||||||
|
await finish(browser, checkAll(out));
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+14
-14
File diff suppressed because one or more lines are too long
@@ -239,6 +239,9 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
|||||||
on the plan after a reload. Same for each tap action and each fill mode
|
on the plan after a reload. Same for each tap action and each fill mode
|
||||||
[auto: backend test_every_display_mode_the_editor_offers_is_accepted and
|
[auto: backend test_every_display_mode_the_editor_offers_is_accepted and
|
||||||
neighbours, test_a_marker_showing_its_value_can_be_saved]
|
neighbours, test_a_marker_showing_its_value_can_be_saved]
|
||||||
|
- [ ] Editor gestures on touch (dev): in the plan editor on a phone, pinch
|
||||||
|
zooms and a moving finger pans; releasing after a gesture does not draw
|
||||||
|
a point, a clean tap still does [auto: smoke_editor_gestures]
|
||||||
- [ ] Legacy geometry parity (v1.50.4, HP-1503-01): a store with a zero
|
- [ ] Legacy geometry parity (v1.50.4, HP-1503-01): a store with a zero
|
||||||
viewport and a negative rect renders identically sane in BOTH cards —
|
viewport and a negative rect renders identically sane in BOTH cards —
|
||||||
full canvas fallback, normalised rectangle [auto: smoke_legacy_geometry]
|
full canvas fallback, normalised rectangle [auto: smoke_legacy_geometry]
|
||||||
|
|||||||
+15
-2
@@ -1416,8 +1416,17 @@ class HouseplanCard extends LitElement {
|
|||||||
clearTimeout(this._kioskHoldTimer);
|
clearTimeout(this._kioskHoldTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// do not interfere with icon dragging and markup drawing
|
// do not interfere with icon and label dragging
|
||||||
if (this._drag || this._markup) return;
|
if (this._drag) return;
|
||||||
|
if (this._markup) {
|
||||||
|
// Drawing is CLICK-based, so gestures coexist with it: a finger that
|
||||||
|
// MOVES pans, two fingers pinch, and _suppressClick keeps the release
|
||||||
|
// from feeding the active tool. The editor used to opt out of gestures
|
||||||
|
// entirely, which left a phone with no way to zoom or pan the plan
|
||||||
|
// (owner's report). Pointers that begin on interactive children still
|
||||||
|
// stay out — labels and handles run their own drags.
|
||||||
|
if ((ev.target as HTMLElement).closest?.('.roomlabel, .rlhandle, .dev, .oplock, .op-hit, button')) return;
|
||||||
|
}
|
||||||
if (this._mode === 'devices' && (ev.target as HTMLElement).closest('.dev')) return;
|
if (this._mode === 'devices' && (ev.target as HTMLElement).closest('.dev')) return;
|
||||||
if (this._mode === 'decor' && this._decorPointerDown(ev)) return;
|
if (this._mode === 'decor' && this._decorPointerDown(ev)) return;
|
||||||
this._pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
this._pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
||||||
@@ -1447,6 +1456,8 @@ class HouseplanCard extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
this._pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
||||||
|
// the tool preview (snap dot, measure label) keeps following the finger
|
||||||
|
if (this._markup && this._pointers.size === 1) this._markupMove(ev);
|
||||||
if (this._pinchStart && this._pointers.size >= 2) {
|
if (this._pinchStart && this._pointers.size >= 2) {
|
||||||
const pts = [...this._pointers.values()];
|
const pts = [...this._pointers.values()];
|
||||||
const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
||||||
@@ -1822,6 +1833,8 @@ class HouseplanCard extends LitElement {
|
|||||||
|
|
||||||
private _markupClick(ev: MouseEvent): void {
|
private _markupClick(ev: MouseEvent): void {
|
||||||
if (!this._markup) return;
|
if (!this._markup) return;
|
||||||
|
// a pan or pinch just happened — the synthesized click is not a draw
|
||||||
|
if (this._suppressClick) return;
|
||||||
// Room cards swallow markup clicks: dragging, resizing or just clicking a
|
// Room cards swallow markup clicks: dragging, resizing or just clicking a
|
||||||
// card must not feed the active tool (draw point, delete room, merge/split
|
// card must not feed the active tool (draw point, delete room, merge/split
|
||||||
// pick, opening placement). The drag itself already stops pointer events,
|
// pick, opening placement). The drag itself already stops pointer events,
|
||||||
|
|||||||
Reference in New Issue
Block a user