@vosjs/studio-core 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +25 -6
- package/dist/index.js +68 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -61,7 +61,13 @@ interface ZoomStyleParams {
|
|
|
61
61
|
autoZoom: boolean;
|
|
62
62
|
/** clicks within this many seconds merge into one span (session merge). */
|
|
63
63
|
clusterGap: number;
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Minimum clicks for a cluster to earn a zoom. Every style is 1: the
|
|
66
|
+
* Cursorful ≥2 rule glide and keynote carried was never in effect on a
|
|
67
|
+
* recorded take (the recorder counted each press twice, so a lone click
|
|
68
|
+
* was a pair on most pages and a stray on the rest), and the lone click on
|
|
69
|
+
* a button is the zoom people miss when it goes.
|
|
70
|
+
*/
|
|
65
71
|
minClusterClicks: number;
|
|
66
72
|
/** zoom so the clicked element fills ~this fraction of the frame. */
|
|
67
73
|
targetFill: number;
|
|
@@ -2023,11 +2029,18 @@ interface SmoothOptions {
|
|
|
2023
2029
|
declare function smoothCursor(track: CursorTrack, options?: SmoothOptions): SmoothPoint[];
|
|
2024
2030
|
|
|
2025
2031
|
/**
|
|
2026
|
-
* A
|
|
2027
|
-
*
|
|
2028
|
-
*
|
|
2029
|
-
*
|
|
2030
|
-
*
|
|
2032
|
+
* A press whose element FITS the frame at less than this level is not a
|
|
2033
|
+
* target: it is a drag (aiming, scrubbing, moving a thing across the canvas)
|
|
2034
|
+
* or a frame-sized surface (a canvas, a panel, a modal clicked to focus or
|
|
2035
|
+
* dismiss it), and a zoom on it says nothing. Five real takes (2026-08-25)
|
|
2036
|
+
* each carried 1-4 clusters of these, planned at the floor level for 10-25s;
|
|
2037
|
+
* every one was dropped by hand. Now they plan nothing.
|
|
2038
|
+
*
|
|
2039
|
+
* The rule is PER PRESS, never per cluster: a target cluster's level came
|
|
2040
|
+
* from its LARGEST element, so one press on a panel inside a chain of button
|
|
2041
|
+
* clicks read the whole chain as a drag and threw every zoom away — two real
|
|
2042
|
+
* takes (2026-09-11) lost 5 of 8 and 4 of 4 clicks that way. A surface press
|
|
2043
|
+
* is set aside before clustering; the targets around it keep their zoom.
|
|
2031
2044
|
*/
|
|
2032
2045
|
declare const DRAG_FIT_LEVEL = 1.15;
|
|
2033
2046
|
interface PlanOptions {
|
|
@@ -2117,6 +2130,12 @@ interface Moment {
|
|
|
2117
2130
|
/** Normalized target bounds (element rect union), when the events had one. */
|
|
2118
2131
|
rect: NormRect | null;
|
|
2119
2132
|
clicks?: number;
|
|
2133
|
+
/**
|
|
2134
|
+
* A click cluster on a frame-sized element (a canvas, a panel, a drag):
|
|
2135
|
+
* the planner frames nothing on it (DRAG_FIT_LEVEL), so a zoom here is
|
|
2136
|
+
* the agent's call, never a proposal.
|
|
2137
|
+
*/
|
|
2138
|
+
surface?: boolean;
|
|
2120
2139
|
pings?: number;
|
|
2121
2140
|
/** Motion in the window, 0..1 (fraction of changed pixels), null without frames. */
|
|
2122
2141
|
activity: number | null;
|
package/dist/index.js
CHANGED
|
@@ -1135,7 +1135,7 @@ var ZOOM_STYLES = {
|
|
|
1135
1135
|
glide: {
|
|
1136
1136
|
autoZoom: true,
|
|
1137
1137
|
clusterGap: 3,
|
|
1138
|
-
minClusterClicks:
|
|
1138
|
+
minClusterClicks: 1,
|
|
1139
1139
|
targetFill: 0.42,
|
|
1140
1140
|
minLevel: 1.3,
|
|
1141
1141
|
maxLevel: 1.8,
|
|
@@ -1308,7 +1308,7 @@ var ZOOM_STYLES = {
|
|
|
1308
1308
|
keynote: {
|
|
1309
1309
|
autoZoom: true,
|
|
1310
1310
|
clusterGap: 3,
|
|
1311
|
-
minClusterClicks:
|
|
1311
|
+
minClusterClicks: 1,
|
|
1312
1312
|
targetFill: 0.42,
|
|
1313
1313
|
minLevel: 1.3,
|
|
1314
1314
|
maxLevel: 1.8,
|
|
@@ -1417,13 +1417,17 @@ var DWELL_MIN = 0.45;
|
|
|
1417
1417
|
var DWELL_MAX = 2.6;
|
|
1418
1418
|
var DWELL_SPACING = 1.8;
|
|
1419
1419
|
var DRAG_FIT_LEVEL = 1.15;
|
|
1420
|
+
var PRESS_ECHO_MS = 4;
|
|
1421
|
+
var PRESS_ECHO_PX = 2;
|
|
1420
1422
|
var TYPING_MIN_PINGS = 2;
|
|
1421
1423
|
var TYPING_MIN_DUR = 0.5;
|
|
1422
1424
|
var TYPING_CLICK_ABSORB = 1.5;
|
|
1423
1425
|
var TYPING_REFOCUS_FRAC = 0.08;
|
|
1424
1426
|
function groupTrack(track, opts) {
|
|
1425
|
-
const { width, height, clusterGap, typingGap, typingZoom } = opts;
|
|
1426
|
-
const clicks =
|
|
1427
|
+
const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts;
|
|
1428
|
+
const clicks = dedupePresses(
|
|
1429
|
+
track.filter((e) => e.type === "down")
|
|
1430
|
+
).map((e) => ({ t: e.t / 1e3, rect: e.rect, x: e.x, y: e.y }));
|
|
1427
1431
|
const sessions = typingZoom ? typingSessions(track, width, height, typingGap) : [];
|
|
1428
1432
|
const absorbed = /* @__PURE__ */ new Set();
|
|
1429
1433
|
for (const s of sessions) {
|
|
@@ -1441,15 +1445,43 @@ function groupTrack(track, opts) {
|
|
|
1441
1445
|
s.events = [best, ...s.events];
|
|
1442
1446
|
}
|
|
1443
1447
|
}
|
|
1444
|
-
const
|
|
1448
|
+
const targets = [];
|
|
1449
|
+
const surfaces = [];
|
|
1445
1450
|
for (const c of clicks) {
|
|
1446
1451
|
if (absorbed.has(c)) continue;
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1452
|
+
if (isSurfacePress(c, width, height, targetFill)) surfaces.push(c);
|
|
1453
|
+
else targets.push(c);
|
|
1454
|
+
}
|
|
1455
|
+
const chain = (list) => {
|
|
1456
|
+
const clusters = [];
|
|
1457
|
+
for (const c of list) {
|
|
1458
|
+
const last = clusters.at(-1);
|
|
1459
|
+
const prev = last?.at(-1);
|
|
1460
|
+
if (last && prev && c.t - prev.t <= clusterGap) last.push(c);
|
|
1461
|
+
else clusters.push([c]);
|
|
1462
|
+
}
|
|
1463
|
+
return clusters;
|
|
1464
|
+
};
|
|
1465
|
+
return { sessions, clusters: chain(targets), surfaces: chain(surfaces) };
|
|
1466
|
+
}
|
|
1467
|
+
function dedupePresses(downs) {
|
|
1468
|
+
const kept = [];
|
|
1469
|
+
for (const e of downs) {
|
|
1470
|
+
const prev = kept.at(-1);
|
|
1471
|
+
if (prev && e.t - prev.t <= PRESS_ECHO_MS && Math.abs(e.x - prev.x) <= PRESS_ECHO_PX && Math.abs(e.y - prev.y) <= PRESS_ECHO_PX) {
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
kept.push(e);
|
|
1451
1475
|
}
|
|
1452
|
-
return
|
|
1476
|
+
return kept;
|
|
1477
|
+
}
|
|
1478
|
+
function fitLevel(rect, width, height, targetFill) {
|
|
1479
|
+
if (!rect || rect.w <= 0 || rect.h <= 0) return null;
|
|
1480
|
+
return Math.min(width * targetFill / rect.w, height * targetFill / rect.h);
|
|
1481
|
+
}
|
|
1482
|
+
function isSurfacePress(c, width, height, targetFill) {
|
|
1483
|
+
const fit = fitLevel(c.rect, width, height, targetFill);
|
|
1484
|
+
return fit !== null && fit < DRAG_FIT_LEVEL;
|
|
1453
1485
|
}
|
|
1454
1486
|
function planAutoZoom(track, options) {
|
|
1455
1487
|
const style = resolveZoomStyle(options.style, options.params);
|
|
@@ -1470,31 +1502,31 @@ function planAutoZoom(track, options) {
|
|
|
1470
1502
|
typingHold = style.typingHold,
|
|
1471
1503
|
typingMinLevel = style.typingMinLevel
|
|
1472
1504
|
} = options;
|
|
1473
|
-
const { sessions, clusters } = groupTrack(track, {
|
|
1505
|
+
const { sessions, clusters, surfaces } = groupTrack(track, {
|
|
1474
1506
|
width,
|
|
1475
1507
|
height,
|
|
1476
1508
|
clusterGap,
|
|
1477
1509
|
typingGap,
|
|
1478
|
-
typingZoom
|
|
1510
|
+
typingZoom,
|
|
1511
|
+
targetFill
|
|
1479
1512
|
});
|
|
1480
1513
|
const eligible = clusters.filter((c) => c.length >= minClusterClicks);
|
|
1481
1514
|
const clickSpans = [];
|
|
1482
|
-
const dragReserved =
|
|
1515
|
+
const dragReserved = surfaces.map((cluster, i) => {
|
|
1516
|
+
const f = clusterFocus(cluster, width, height);
|
|
1517
|
+
return {
|
|
1518
|
+
id: `drag${i}`,
|
|
1519
|
+
in: Math.max(0, cluster[0].t - lead),
|
|
1520
|
+
out: cluster[cluster.length - 1].t + hold,
|
|
1521
|
+
level: 1,
|
|
1522
|
+
cx: f.cx,
|
|
1523
|
+
cy: f.cy
|
|
1524
|
+
};
|
|
1525
|
+
});
|
|
1483
1526
|
for (const cluster of eligible) {
|
|
1484
1527
|
const first = cluster[0];
|
|
1485
1528
|
const last = cluster[cluster.length - 1];
|
|
1486
1529
|
const f = focusFor(cluster, width, height, targetFill, minLevel, maxLevel);
|
|
1487
|
-
if (f.fit !== null && f.fit < DRAG_FIT_LEVEL) {
|
|
1488
|
-
dragReserved.push({
|
|
1489
|
-
id: `drag${dragReserved.length}`,
|
|
1490
|
-
in: Math.max(0, first.t - lead),
|
|
1491
|
-
out: last.t + hold,
|
|
1492
|
-
level: 1,
|
|
1493
|
-
cx: f.cx,
|
|
1494
|
-
cy: f.cy
|
|
1495
|
-
});
|
|
1496
|
-
continue;
|
|
1497
|
-
}
|
|
1498
1530
|
clickSpans.push({
|
|
1499
1531
|
in: Math.max(0, first.t - lead),
|
|
1500
1532
|
out: last.t + hold,
|
|
@@ -1668,15 +1700,8 @@ function focusFor(cluster, width, height, targetFill, minLevel, maxLevel) {
|
|
|
1668
1700
|
const n = cluster.length;
|
|
1669
1701
|
const cx = clamp012(sx / n / width);
|
|
1670
1702
|
const cy = clamp012(sy / n / height);
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
if (maxW > 0 && maxH > 0) {
|
|
1674
|
-
const fitX = width * targetFill / maxW;
|
|
1675
|
-
const fitY = height * targetFill / maxH;
|
|
1676
|
-
level = Math.min(fitX, fitY);
|
|
1677
|
-
fit = level;
|
|
1678
|
-
}
|
|
1679
|
-
return { cx, cy, level: clamp2(level, minLevel, maxLevel), fit };
|
|
1703
|
+
const level = fitLevel({ x: 0, y: 0, w: maxW, h: maxH }, width, height, targetFill) ?? maxLevel;
|
|
1704
|
+
return { cx, cy, level: clamp2(level, minLevel, maxLevel) };
|
|
1680
1705
|
}
|
|
1681
1706
|
function clusterFocus(events, width, height) {
|
|
1682
1707
|
let sx = 0;
|
|
@@ -6962,14 +6987,15 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
6962
6987
|
rect: null
|
|
6963
6988
|
});
|
|
6964
6989
|
if (track.length) {
|
|
6965
|
-
const { sessions, clusters } = groupTrack(track, {
|
|
6990
|
+
const { sessions, clusters, surfaces } = groupTrack(track, {
|
|
6966
6991
|
width,
|
|
6967
6992
|
height,
|
|
6968
6993
|
clusterGap: style.clusterGap,
|
|
6969
6994
|
typingGap: style.typingGap,
|
|
6970
|
-
typingZoom: style.typingZoom
|
|
6995
|
+
typingZoom: style.typingZoom,
|
|
6996
|
+
targetFill: style.targetFill
|
|
6971
6997
|
});
|
|
6972
|
-
|
|
6998
|
+
const pressed = (c, surface) => {
|
|
6973
6999
|
const f = clusterFocus(c, width, height);
|
|
6974
7000
|
drafts.push({
|
|
6975
7001
|
kind: "click",
|
|
@@ -6980,9 +7006,12 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
6980
7006
|
at: c[0].t,
|
|
6981
7007
|
focus: { cx: f.cx, cy: f.cy },
|
|
6982
7008
|
rect: f.rect,
|
|
6983
|
-
clicks: c.length
|
|
7009
|
+
clicks: c.length,
|
|
7010
|
+
...surface ? { surface: true } : {}
|
|
6984
7011
|
});
|
|
6985
|
-
}
|
|
7012
|
+
};
|
|
7013
|
+
for (const c of clusters) pressed(c, false);
|
|
7014
|
+
for (const c of surfaces) pressed(c, true);
|
|
6986
7015
|
for (const s of sessions) {
|
|
6987
7016
|
const f = clusterFocus(s.events, width, height);
|
|
6988
7017
|
drafts.push({
|
|
@@ -7102,6 +7131,7 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
7102
7131
|
h: round8(d.rect.h)
|
|
7103
7132
|
} : null,
|
|
7104
7133
|
...d.clicks !== void 0 ? { clicks: d.clicks } : {},
|
|
7134
|
+
...d.surface ? { surface: true } : {},
|
|
7105
7135
|
...d.pings !== void 0 ? { pings: d.pings } : {},
|
|
7106
7136
|
activity: activityOf(opts.bins, d),
|
|
7107
7137
|
proposed,
|