fix(studio): a touch anywhere on the ruler takes the number, on every engine

This commit is contained in:
2026-10-13 23:06:27 +07:00
parent b6ae819002
commit ad85f0bd0a
3 changed files with 120 additions and 3 deletions
+62 -1
View File
@@ -10,7 +10,9 @@
// number stops at its own ends. It holds down the two things that could be lost
// to it as well: two fingers are still the phone's own zoom (a pinch is not a
// value, so the finger's hold is given up the moment the second one lands), and
// with the row put away the same drag changes nothing at all.
// with the row put away the same drag changes nothing at all. The row's own
// track takes a touch anywhere on it too, whatever the engine would have done
// with the thumb's 3px box.
//
// npm run build && node scripts/studio-drag-check.mjs
// PLAYWRIGHT_CORE=<path to playwright-core/index.mjs> CHROME=<path to a chromium> \
@@ -321,6 +323,65 @@ await drag([box.cx, box.cy], [-box.w * 0.25, 0]);
const down = await rulerValue();
step('dragged to the left the knob comes down with it', Math.abs(down - 50) <= 1, `CONTRAST ${down}`);
// --- the hand on the ruler itself --------------------------------------------
// The other half of the same trade, and the one an engine can take away: the
// number can be had by the hand that lands on the ruler, not only by the one
// walking across the photo. WebKit begins a slider drag only when the touch
// falls inside the thumb's own box — a 3px line here (see `SliderThumbElement::
// handleTouchStart`) — so on a phone the ticks across the track answered a finger
// while the middle of the ruler answered nothing. The row reads the value from
// the touch's own place on the track, so what is made here is the gesture a
// browser's slider would take: an untrusted `PointerEvent`, which Blink's own
// slider ignores and the row's own listener does not. The whole gesture is put
// down on one pixel of the box it started on, the way a hand is.
const trackTouch = (rail, fraction, type = 'pointerdown', pointerId = 7, pointerType = 'touch') =>
page.$eval(
'[data-col="slider"] input[type="range"]',
(e, p) => {
e.dispatchEvent(
new PointerEvent(p.type, {
bubbles: true,
cancelable: true,
composed: true,
pointerId: p.pointerId,
pointerType: p.pointerType,
isPrimary: p.pointerId === 7,
buttons: 1,
clientX: p.rail.left + p.rail.width * p.fraction,
clientY: p.rail.mid,
})
);
},
{ rail, fraction, type, pointerId, pointerType }
);
// The ruler's own box, taken once: a hand on it is a fixed pixel, so the whole
// gesture is measured against the box it started on.
const trackRect = () =>
page.$eval('[data-col="slider"] input[type="range"]', (e) => {
const r = e.getBoundingClientRect();
return { left: r.left, width: r.width, mid: r.top + r.height / 2, value: Number(e.value) };
});
const rail = await trackRect();
await trackTouch(rail, 0.85);
const onTrack = await rulerValue();
step('a touch on the ruler’s own track takes the number from where it landed', Math.abs(onTrack - 70) <= 1, `CONTRAST ${onTrack} at 85% of the track`);
const steady = (await trackRect()).width;
await trackTouch(rail, 0.5, 'pointermove');
step('and the number walks with the hand along the ruler', Math.abs(await rulerValue()) <= 1, `CONTRAST ${await rulerValue()} at the middle`);
step('and the track stays under the hand while the number changes length', (await trackRect()).width === steady, `${steady} -> ${(await trackRect()).width}`);
const sole = await rulerValue();
await trackTouch(rail, 0.25, 'pointermove', 9);
step('a second finger is not a second value', (await rulerValue()) === sole, `CONTRAST ${sole} -> ${await rulerValue()}`);
await trackTouch(rail, 0.2, 'pointermove');
step('and the number comes down as the hand goes back', Math.abs((await rulerValue()) + 60) <= 1, `CONTRAST ${await rulerValue()} at a fifth`);
await trackTouch(rail, 0.5, 'pointerup');
const lifted = await rulerValue();
await trackTouch(rail, 0.95, 'pointermove');
step('the number stands where the finger was lifted', (await rulerValue()) === lifted, `CONTRAST ${lifted} -> ${await rulerValue()}`);
await trackTouch(rail, 0.1, 'pointerdown', 11, 'mouse');
step('and the mouse keeps the drag the engines already give it', (await rulerValue()) === lifted, `CONTRAST ${lifted} -> ${await rulerValue()}`);
// --- with the row put away ---------------------------------------------------
// The drag belongs to the ruler, not to the photo, and a phone puts the row away
// through the path under the strip — the strip it came from is off the screen