waygraph 0.12.48 → 0.13.1
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/cli.js +202 -12
- package/dist/highlights.d.ts +93 -7
- package/dist/highlights.js +231 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +2 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +4 -1
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +2 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +4 -1
package/dist/cli.js
CHANGED
|
@@ -230,6 +230,8 @@ import {
|
|
|
230
230
|
applyTodoPhase,
|
|
231
231
|
advanceTodoDock,
|
|
232
232
|
completeSequentialTodoDock,
|
|
233
|
+
applyDevicePhase,
|
|
234
|
+
resolveDeviceState,
|
|
233
235
|
} from "waygraph";
|
|
234
236
|
|
|
235
237
|
function walkDir(dir, pattern) {
|
|
@@ -530,6 +532,18 @@ const RING_CSS =
|
|
|
530
532
|
"#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
|
|
531
533
|
"#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
|
|
532
534
|
"#wg-todo-dock #wg-todos{list-style:none;margin:0;padding:0;background:transparent;border:none;}" +
|
|
535
|
+
// Device / touch badge (0.13+) - shows preset + remain|desktop reset.
|
|
536
|
+
"#wg-device-badge{position:fixed;z-index:2147483646;top:14px;right:14px;" +
|
|
537
|
+
"padding:6px 10px;border-radius:999px;background:rgba(20,10,40,.92);color:#f0e8ff;" +
|
|
538
|
+
"border:1px solid rgba(124,58,237,.5);font:700 11px/1.2 system-ui,sans-serif;" +
|
|
539
|
+
"letter-spacing:.04em;text-transform:uppercase;pointer-events:none;" +
|
|
540
|
+
"box-shadow:0 4px 14px rgba(0,0,0,.3);opacity:0;transition:opacity .25s ease;}" +
|
|
541
|
+
"#wg-device-badge.wg-in{opacity:1;}" +
|
|
542
|
+
"#wg-device-badge[data-preset=mobile]{border-color:#3B82F6;}" +
|
|
543
|
+
"#wg-device-badge[data-preset=tablet]{border-color:#22C55E;}" +
|
|
544
|
+
"#wg-device-badge[data-preset=desktop]{border-color:#9CA3AF;}" +
|
|
545
|
+
"#wg-device-badge[data-touch=1]::after{content:' · touch';opacity:.85;}" +
|
|
546
|
+
"#wg-cursor[data-touch=1]{width:28px;height:28px;}" +
|
|
533
547
|
"#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
|
|
534
548
|
"border:1px solid #3a2a60;}" +
|
|
535
549
|
"#wg-todos li{display:flex;gap:8px;align-items:flex-start;margin:0 0 6px;font:600 12.5px/1.35 system-ui,sans-serif;}" +
|
|
@@ -538,6 +552,9 @@ const RING_CSS =
|
|
|
538
552
|
"#wg-todos .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.85;}" +
|
|
539
553
|
"#wg-todos .wg-todo-current{color:#fff;}" +
|
|
540
554
|
"#wg-todos .wg-todo-pending{color:#9a7ad1;}" +
|
|
555
|
+
"#wg-todos[data-wg-todo-style=bullets] .wg-todo-pending," +
|
|
556
|
+
"#wg-todos[data-wg-todo-style=bullets] .wg-todo-current{color:#e8dcff;}" +
|
|
557
|
+
"#wg-todos[data-wg-todo-style=bullets] .wg-todo-mark{color:#c9a6ff;}" +
|
|
541
558
|
"#wg-progress{height:4px;background:#2a1650;border-radius:2px;margin:0 0 12px;overflow:hidden;}" +
|
|
542
559
|
"#wg-progress-bar{height:100%;background:#7C3AED;border-radius:2px;transition:width .3s ease;}" +
|
|
543
560
|
// A real tab bar for episodes - the currently-active episode reads as
|
|
@@ -1182,21 +1199,39 @@ async function installOverlay(page, title) {
|
|
|
1182
1199
|
return id ? ' data-wg-todo-item="' + esc(id) + '"' : "";
|
|
1183
1200
|
};
|
|
1184
1201
|
const renderItems = (items, style) => {
|
|
1185
|
-
const
|
|
1202
|
+
const rawStyle = String(style || "sequential").toLowerCase();
|
|
1203
|
+
const st =
|
|
1204
|
+
rawStyle === "checklist"
|
|
1205
|
+
? "checklist"
|
|
1206
|
+
: rawStyle === "bullets" ||
|
|
1207
|
+
rawStyle === "bullet" ||
|
|
1208
|
+
rawStyle === "list" ||
|
|
1209
|
+
rawStyle === "ul" ||
|
|
1210
|
+
rawStyle === "points" ||
|
|
1211
|
+
rawStyle === "plain"
|
|
1212
|
+
? "bullets"
|
|
1213
|
+
: "sequential";
|
|
1186
1214
|
return (
|
|
1187
1215
|
'<ul id="wg-todos" data-wg-todo-style="' +
|
|
1188
1216
|
st +
|
|
1189
1217
|
'">' +
|
|
1190
1218
|
(items || [])
|
|
1191
1219
|
.map((t) => {
|
|
1192
|
-
const cls =
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1220
|
+
const cls =
|
|
1221
|
+
st === "bullets"
|
|
1222
|
+
? t.done
|
|
1223
|
+
? "wg-todo-done"
|
|
1224
|
+
: "wg-todo-pending"
|
|
1225
|
+
: t.current
|
|
1226
|
+
? "wg-todo-current"
|
|
1227
|
+
: t.done
|
|
1228
|
+
? "wg-todo-done"
|
|
1229
|
+
: "wg-todo-pending";
|
|
1197
1230
|
let mark;
|
|
1198
1231
|
if (st === "checklist") {
|
|
1199
1232
|
mark = t.done ? "\u2611" : "\u2610";
|
|
1233
|
+
} else if (st === "bullets") {
|
|
1234
|
+
mark = "\u2022";
|
|
1200
1235
|
} else {
|
|
1201
1236
|
mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
|
|
1202
1237
|
}
|
|
@@ -1252,6 +1287,64 @@ async function installOverlay(page, title) {
|
|
|
1252
1287
|
window.__wgStampModal(dock, "todos", { ready: true });
|
|
1253
1288
|
}
|
|
1254
1289
|
};
|
|
1290
|
+
// Device / touch badge (0.13+) - mirror todo sync keep/set/clear.
|
|
1291
|
+
// Payload: { sync, device?, remain? }
|
|
1292
|
+
window.__wgSyncDevice = (payload) => {
|
|
1293
|
+
const sync =
|
|
1294
|
+
payload && (payload.sync === "clear" || payload.sync === "keep" || payload.sync === "set")
|
|
1295
|
+
? payload.sync
|
|
1296
|
+
: "keep";
|
|
1297
|
+
const device = payload && payload.device ? payload.device : null;
|
|
1298
|
+
let badge = document.getElementById("wg-device-badge");
|
|
1299
|
+
const cursor = document.getElementById("wg-cursor");
|
|
1300
|
+
const MOUSE_SVG =
|
|
1301
|
+
"<svg viewBox='0 0 32 32' width='24' height='24'>" +
|
|
1302
|
+
"<path fill='#0C0C1A' stroke='#fff' stroke-width='1.4' stroke-linejoin='round' " +
|
|
1303
|
+
"d='M6 3.5l1.4 22.5 5.8-5.4 4.2 9.4 3.6-1.6-4.2-9.2H26z'/></svg>";
|
|
1304
|
+
const FINGER_SVG =
|
|
1305
|
+
"<svg viewBox='0 0 32 32' width='28' height='28'>" +
|
|
1306
|
+
"<ellipse cx='16' cy='22' rx='7' ry='8' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/>" +
|
|
1307
|
+
"<rect x='12' y='6' width='8' height='16' rx='4' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/>" +
|
|
1308
|
+
"</svg>";
|
|
1309
|
+
if (sync === "keep" && !device) return;
|
|
1310
|
+
if (sync === "clear" || (device && device.preset === "desktop" && !device.touchMode)) {
|
|
1311
|
+
if (badge) badge.remove();
|
|
1312
|
+
if (cursor) {
|
|
1313
|
+
cursor.removeAttribute("data-touch");
|
|
1314
|
+
cursor.innerHTML = MOUSE_SVG;
|
|
1315
|
+
}
|
|
1316
|
+
if (sync === "clear" && !device) return;
|
|
1317
|
+
// clear with desktop device: still hide badge (back to main)
|
|
1318
|
+
if (device && device.preset === "desktop" && !device.touchMode) return;
|
|
1319
|
+
}
|
|
1320
|
+
if (!device) return;
|
|
1321
|
+
const preset = device.preset || "desktop";
|
|
1322
|
+
const touch = !!device.touchMode;
|
|
1323
|
+
const remain = payload.remain !== false && sync !== "clear";
|
|
1324
|
+
if (!badge) {
|
|
1325
|
+
badge = document.createElement("div");
|
|
1326
|
+
badge.id = "wg-device-badge";
|
|
1327
|
+
badge.setAttribute("data-wg-ui", "1");
|
|
1328
|
+
document.documentElement.appendChild(badge);
|
|
1329
|
+
}
|
|
1330
|
+
badge.dataset.preset = preset;
|
|
1331
|
+
badge.dataset.touch = touch ? "1" : "0";
|
|
1332
|
+
const label =
|
|
1333
|
+
preset === "desktop" && !touch
|
|
1334
|
+
? "desktop"
|
|
1335
|
+
: preset + (remain ? " · remain" : " · set");
|
|
1336
|
+
badge.textContent = label;
|
|
1337
|
+
badge.classList.add("wg-in");
|
|
1338
|
+
if (cursor) {
|
|
1339
|
+
if (touch) {
|
|
1340
|
+
cursor.setAttribute("data-touch", "1");
|
|
1341
|
+
cursor.innerHTML = FINGER_SVG;
|
|
1342
|
+
} else {
|
|
1343
|
+
cursor.removeAttribute("data-touch");
|
|
1344
|
+
cursor.innerHTML = MOUSE_SVG;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1255
1348
|
},
|
|
1256
1349
|
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay },
|
|
1257
1350
|
)
|
|
@@ -2681,6 +2774,49 @@ async function clickPulseAt(page, x, y, tone) {
|
|
|
2681
2774
|
.catch(() => {});
|
|
2682
2775
|
}
|
|
2683
2776
|
|
|
2777
|
+
/**
|
|
2778
|
+
* Apply authored device fixture to the live page (0.13+).
|
|
2779
|
+
* Uses the real setViewportSize (step mode noops block-authored calls).
|
|
2780
|
+
* sync keep with no device = no-op; clear / desktop = restore main feel.
|
|
2781
|
+
*/
|
|
2782
|
+
async function applyDeviceToPage(page, device, sync) {
|
|
2783
|
+
const mode = sync || (device ? "set" : "keep");
|
|
2784
|
+
if (mode === "keep" && !device) return;
|
|
2785
|
+
const d =
|
|
2786
|
+
device ||
|
|
2787
|
+
resolveDeviceState("desktop", false) || {
|
|
2788
|
+
preset: "desktop",
|
|
2789
|
+
viewport: { width: 1280, height: 720, deviceScaleFactor: 1 },
|
|
2790
|
+
isMobile: false,
|
|
2791
|
+
hasTouch: false,
|
|
2792
|
+
touchMode: false,
|
|
2793
|
+
};
|
|
2794
|
+
const realSet =
|
|
2795
|
+
typeof page.__wgRealSetViewportSize === "function"
|
|
2796
|
+
? page.__wgRealSetViewportSize
|
|
2797
|
+
: page.setViewportSize.bind(page);
|
|
2798
|
+
try {
|
|
2799
|
+
await realSet({
|
|
2800
|
+
width: d.viewport.width,
|
|
2801
|
+
height: d.viewport.height,
|
|
2802
|
+
});
|
|
2803
|
+
} catch {
|
|
2804
|
+
/* maximized / null viewport hosts may reject - badge still updates */
|
|
2805
|
+
}
|
|
2806
|
+
await page
|
|
2807
|
+
.evaluate(
|
|
2808
|
+
(payload) => {
|
|
2809
|
+
if (window.__wgSyncDevice) window.__wgSyncDevice(payload);
|
|
2810
|
+
},
|
|
2811
|
+
{
|
|
2812
|
+
sync: mode,
|
|
2813
|
+
device: d,
|
|
2814
|
+
remain: mode === "keep" || mode === "set",
|
|
2815
|
+
},
|
|
2816
|
+
)
|
|
2817
|
+
.catch(() => {});
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2684
2820
|
/**
|
|
2685
2821
|
* True if the engine's own narrate() (waygraph's public export, for a Block
|
|
2686
2822
|
* author explicitly captioning one action) JUST positioned the ring for
|
|
@@ -2697,7 +2833,7 @@ async function wasJustNarrated(page) {
|
|
|
2697
2833
|
.catch(() => false);
|
|
2698
2834
|
}
|
|
2699
2835
|
|
|
2700
|
-
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef) {
|
|
2836
|
+
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef, deviceRef) {
|
|
2701
2837
|
// Playwright's own slowMo ALREADY pauses after every single low-level
|
|
2702
2838
|
// action it dispatches - and pressSequentially() fires one such action
|
|
2703
2839
|
// PER CHARACTER. Also giving pressSequentially its own fixed delay
|
|
@@ -2861,7 +2997,36 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2861
2997
|
// mock click visually completes before the real one fires.
|
|
2862
2998
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2863
2999
|
}
|
|
2864
|
-
|
|
3000
|
+
// Touch theater (0.13+): prefer Locator.tap / long-press when device
|
|
3001
|
+
// touchMode is on (context launched with hasTouch). gesture on stub
|
|
3002
|
+
// wins: tap | hold | click.
|
|
3003
|
+
let result;
|
|
3004
|
+
const stubsForGesture = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
3005
|
+
let gesture = "click";
|
|
3006
|
+
try {
|
|
3007
|
+
const idx = await matchStubIndexForLocator(page, this, stubsForGesture);
|
|
3008
|
+
const stub = idx >= 0 ? stubsForGesture[idx] : null;
|
|
3009
|
+
if (stub && stub.gesture) gesture = stub.gesture;
|
|
3010
|
+
else if (deviceRef && deviceRef.current && deviceRef.current.touchMode) gesture = "tap";
|
|
3011
|
+
} catch {
|
|
3012
|
+
if (deviceRef && deviceRef.current && deviceRef.current.touchMode) gesture = "tap";
|
|
3013
|
+
}
|
|
3014
|
+
if (gesture === "tap" || gesture === "hold") {
|
|
3015
|
+
try {
|
|
3016
|
+
if (gesture === "hold") {
|
|
3017
|
+
result = await this.tap({
|
|
3018
|
+
...(options || {}),
|
|
3019
|
+
delay: skipTheater() ? 0 : 600,
|
|
3020
|
+
});
|
|
3021
|
+
} else {
|
|
3022
|
+
result = await this.tap(options);
|
|
3023
|
+
}
|
|
3024
|
+
} catch {
|
|
3025
|
+
result = await originalClick.call(this, options);
|
|
3026
|
+
}
|
|
3027
|
+
} else {
|
|
3028
|
+
result = await originalClick.call(this, options);
|
|
3029
|
+
}
|
|
2865
3030
|
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2866
3031
|
await hideRing(page);
|
|
2867
3032
|
return result;
|
|
@@ -2971,7 +3136,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2971
3136
|
const stubBeforeRef = { current: [] };
|
|
2972
3137
|
/** Live todo dock for Method fill/click advance (same carry as lastTodoDock). */
|
|
2973
3138
|
const todoDockRef = { current: undefined };
|
|
2974
|
-
|
|
3139
|
+
/** Live device fixture for touch theater (same carry as lastDevice). */
|
|
3140
|
+
const deviceRef = { current: undefined };
|
|
3141
|
+
instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef, deviceRef);
|
|
2975
3142
|
// Force the panel checkbox from this process's flags/env at run start.
|
|
2976
3143
|
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
2977
3144
|
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
@@ -3002,7 +3169,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3002
3169
|
// setViewportSize entirely for the duration of step mode. Blocks that
|
|
3003
3170
|
// call it don't need it to actually do anything here - their own
|
|
3004
3171
|
// selectors/layouts still work fine at whatever size the real window
|
|
3005
|
-
// already is.
|
|
3172
|
+
// already is. Authored ctx.device() still uses __wgRealSetViewportSize.
|
|
3173
|
+
page.__wgRealSetViewportSize = page.setViewportSize.bind(page);
|
|
3006
3174
|
page.setViewportSize = async () => {};
|
|
3007
3175
|
let resolveNext = null;
|
|
3008
3176
|
// Render-to-gate race: a slide/step panel's Next button is wired to
|
|
@@ -3100,6 +3268,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3100
3268
|
let result;
|
|
3101
3269
|
/** Carry floating todo dock across steps (empty stubBefore must not wipe). */
|
|
3102
3270
|
let lastTodoDock = undefined;
|
|
3271
|
+
/** Carry device / touch fixture across steps (omit = keep, like todos). */
|
|
3272
|
+
let lastDevice = undefined;
|
|
3103
3273
|
for (let i = 0; i < resolved.length; i++) {
|
|
3104
3274
|
const r = resolved[i];
|
|
3105
3275
|
// The block breadcrumb is scoped to THIS step's own episode, not the
|
|
@@ -3193,6 +3363,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3193
3363
|
});
|
|
3194
3364
|
lastTodoDock = appliedBefore.dock;
|
|
3195
3365
|
todoDockRef.current = lastTodoDock;
|
|
3366
|
+
const appliedDeviceBefore = applyDevicePhase(lastDevice, {
|
|
3367
|
+
deviceSync: stubBeforePhase.deviceSync,
|
|
3368
|
+
device: stubBeforePhase.device,
|
|
3369
|
+
});
|
|
3370
|
+
lastDevice = appliedDeviceBefore.device;
|
|
3371
|
+
deviceRef.current = lastDevice;
|
|
3372
|
+
if (appliedDeviceBefore.sync !== "keep" || lastDevice) {
|
|
3373
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceBefore.sync);
|
|
3374
|
+
}
|
|
3196
3375
|
const stubBeforeTodos =
|
|
3197
3376
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3198
3377
|
stubBeforePhase.todos ||
|
|
@@ -3418,6 +3597,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3418
3597
|
let afterOverlayTitle = overlayTitle;
|
|
3419
3598
|
let afterTodoPos = overlayTodoPos;
|
|
3420
3599
|
let appliedAfter = { dock: lastTodoDock, sync: "keep" };
|
|
3600
|
+
let appliedDeviceAfter = { device: lastDevice, sync: "keep" };
|
|
3421
3601
|
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
3422
3602
|
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
3423
3603
|
out: result,
|
|
@@ -3431,6 +3611,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3431
3611
|
});
|
|
3432
3612
|
lastTodoDock = appliedAfter.dock;
|
|
3433
3613
|
todoDockRef.current = lastTodoDock;
|
|
3614
|
+
appliedDeviceAfter = applyDevicePhase(lastDevice, {
|
|
3615
|
+
deviceSync: afterPhase.deviceSync,
|
|
3616
|
+
device: afterPhase.device,
|
|
3617
|
+
});
|
|
3618
|
+
lastDevice = appliedDeviceAfter.device;
|
|
3619
|
+
deviceRef.current = lastDevice;
|
|
3620
|
+
if (appliedDeviceAfter.sync !== "keep" || (appliedDeviceAfter.sync === "set" && lastDevice)) {
|
|
3621
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceAfter.sync);
|
|
3622
|
+
}
|
|
3434
3623
|
stubAfterTodos =
|
|
3435
3624
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3436
3625
|
afterPhase.todos ||
|
|
@@ -3880,10 +4069,11 @@ async function main() {
|
|
|
3880
4069
|
// Lock DPR so --video-viewport 800x450 stays 800x450 in the .webm
|
|
3881
4070
|
// (HiDPI hosts otherwise scale recordVideo to e.g. 1920x1080).
|
|
3882
4071
|
deviceScaleFactor: 1,
|
|
4072
|
+
hasTouch: true,
|
|
3883
4073
|
}
|
|
3884
4074
|
: step
|
|
3885
|
-
? { baseURL, viewport: null }
|
|
3886
|
-
: { baseURL, viewport: { width: 1280, height: 720 } };
|
|
4075
|
+
? { baseURL, viewport: null, hasTouch: true }
|
|
4076
|
+
: { baseURL, viewport: { width: 1280, height: 720 }, hasTouch: true };
|
|
3887
4077
|
if (videoDir) {
|
|
3888
4078
|
contextOpts.recordVideo = fixedVideoViewport
|
|
3889
4079
|
? { dir: videoDir, size: fixedVideoViewport }
|
package/dist/highlights.d.ts
CHANGED
|
@@ -51,8 +51,9 @@ export type WaygraphTodoInput = string | WaygraphTodoItem;
|
|
|
51
51
|
* Dock list presentation:
|
|
52
52
|
* - `sequential` - arrow on current, strike done (demo walkthrough)
|
|
53
53
|
* - `checklist` - checkbox done/pending only (no current arrow; independent checks)
|
|
54
|
+
* - `bullets` - flat bullet list (no arrow/checkbox; open notes / trimmed dock)
|
|
54
55
|
*/
|
|
55
|
-
export type TodoListStyle = "sequential" | "checklist";
|
|
56
|
+
export type TodoListStyle = "sequential" | "checklist" | "bullets";
|
|
56
57
|
/** One titled group in the floating todo dock (FR / Scenarios / ACs, etc.). */
|
|
57
58
|
export type WaygraphTodoGroupInput = {
|
|
58
59
|
/** Stable group id -> `data-wg-todo-group`. */
|
|
@@ -99,7 +100,7 @@ export type StubPhaseFixtures = {
|
|
|
99
100
|
* (e.g. Mailhog -> back to app and still find the checklist).
|
|
100
101
|
*/
|
|
101
102
|
todoId?: string;
|
|
102
|
-
/** `sequential` (default) or `
|
|
103
|
+
/** `sequential` (default), `checklist`, or `bullets`. */
|
|
103
104
|
todoStyle?: TodoListStyle;
|
|
104
105
|
/** Multiple titled lists (FR / Scenarios / ACs). Wins over bare todos when set. */
|
|
105
106
|
todoGroups?: readonly WaygraphTodoGroupInput[];
|
|
@@ -117,6 +118,57 @@ export type StubPhaseFixtures = {
|
|
|
117
118
|
* Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
|
|
118
119
|
*/
|
|
119
120
|
todoPos?: "left" | "right";
|
|
121
|
+
/**
|
|
122
|
+
* Viewport preset: `mobile` | `tablet` | `desktop` (default / clear).
|
|
123
|
+
* Omit on later blocks = **keep** (same persist pattern as todos).
|
|
124
|
+
*/
|
|
125
|
+
device?: DevicePreset | DeviceState;
|
|
126
|
+
/** Explicit pixel viewport (wins over preset width/height when both set). */
|
|
127
|
+
viewport?: DeviceViewport;
|
|
128
|
+
/** Touch theater + Playwright touchscreen when true. */
|
|
129
|
+
touch?: boolean;
|
|
130
|
+
};
|
|
131
|
+
/** Named viewport presets (0.13+). */
|
|
132
|
+
export type DevicePreset = "mobile" | "tablet" | "desktop";
|
|
133
|
+
/** Explicit viewport box. */
|
|
134
|
+
export type DeviceViewport = {
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
deviceScaleFactor?: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Carried device fixture (todo-dock shaped persist).
|
|
141
|
+
* Omit on a later step = keep; hideDevice/clearDevice/desktop = clear to desktop.
|
|
142
|
+
*/
|
|
143
|
+
export type DeviceState = {
|
|
144
|
+
preset: DevicePreset;
|
|
145
|
+
viewport: DeviceViewport;
|
|
146
|
+
/** Playwright-ish: treat as mobile layout. */
|
|
147
|
+
isMobile: boolean;
|
|
148
|
+
/** Context needs hasTouch for touchscreen.tap (demo launches with hasTouch). */
|
|
149
|
+
hasTouch: boolean;
|
|
150
|
+
/** Demo gesture theater: finger cursor, tap/hold timing, prefer touchscreen. */
|
|
151
|
+
touchMode: boolean;
|
|
152
|
+
};
|
|
153
|
+
/** Built-in preset sizes (CSS pixels). */
|
|
154
|
+
export declare const DEVICE_PRESETS: Record<DevicePreset, Omit<DeviceState, "touchMode"> & {
|
|
155
|
+
touchMode?: boolean;
|
|
156
|
+
}>;
|
|
157
|
+
export declare function normalizeDevicePreset(raw: unknown): DevicePreset | undefined;
|
|
158
|
+
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
159
|
+
export declare function resolveDeviceState(input: DevicePreset | DeviceState | DeviceViewport | undefined, touchOverride?: boolean): DeviceState | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
162
|
+
* - clear: hideDevice / clearDevice / device("desktop") with clear intent
|
|
163
|
+
* - set: author set device/viewport/touch this phase
|
|
164
|
+
* - keep: omit - preserve previous (whole episode remain)
|
|
165
|
+
*/
|
|
166
|
+
export declare function applyDevicePhase(prev: DeviceState | undefined, phase: {
|
|
167
|
+
deviceSync?: "set" | "clear" | "keep";
|
|
168
|
+
device?: DeviceState;
|
|
169
|
+
}): {
|
|
170
|
+
device: DeviceState | undefined;
|
|
171
|
+
sync: "set" | "clear" | "keep";
|
|
120
172
|
};
|
|
121
173
|
/** One named highlight slot on a block (selector + caption). */
|
|
122
174
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
@@ -176,6 +228,13 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
176
228
|
* for authored stub queues; set false to keep cursor hidden).
|
|
177
229
|
*/
|
|
178
230
|
cursor?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Pointer feel when touchMode is on (0.13+):
|
|
233
|
+
* - `tap` - short press via touchscreen (default in touch mode)
|
|
234
|
+
* - `hold` - long-press (~600ms) then release
|
|
235
|
+
* - `click` - mouse click even in touch mode
|
|
236
|
+
*/
|
|
237
|
+
gesture?: "tap" | "hold" | "click";
|
|
179
238
|
}
|
|
180
239
|
/** Flow fixture patch: label required; selector optional (inherits from stub). */
|
|
181
240
|
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom" | "zoomOut" | "focus" | "followMouse" | "follow" | "color" | "cursor"> & FixtureDurationFields;
|
|
@@ -222,8 +281,8 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
222
281
|
*/
|
|
223
282
|
todoId(id: string): void;
|
|
224
283
|
/**
|
|
225
|
-
* Dock presentation: `sequential` (arrow walkthrough)
|
|
226
|
-
* (independent checkboxes, no
|
|
284
|
+
* Dock presentation: `sequential` (arrow walkthrough), `checklist`
|
|
285
|
+
* (independent checkboxes), or `bullets` (flat • list, no progress marks).
|
|
227
286
|
*/
|
|
228
287
|
todoStyle(style: TodoListStyle): void;
|
|
229
288
|
/**
|
|
@@ -238,6 +297,23 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
238
297
|
hideTodos(): void;
|
|
239
298
|
/** Alias for {@link StubCtx.hideTodos}. */
|
|
240
299
|
clearTodos(): void;
|
|
300
|
+
/**
|
|
301
|
+
* Viewport / device fixture (0.13+). `mobile` | `tablet` | `desktop`.
|
|
302
|
+
* Omit on later blocks = **keep** (same persist as todos). Use
|
|
303
|
+
* {@link StubCtx.hideDevice} / {@link StubCtx.clearDevice} to return to desktop.
|
|
304
|
+
*/
|
|
305
|
+
device(preset: DevicePreset | DeviceState): void;
|
|
306
|
+
/** Explicit CSS-pixel viewport (implies mobile-ish unless desktop-sized). */
|
|
307
|
+
viewport(box: DeviceViewport): void;
|
|
308
|
+
/** Enable / disable touch theater + touchscreen gestures. */
|
|
309
|
+
touch(on?: boolean): void;
|
|
310
|
+
/**
|
|
311
|
+
* Reset to desktop viewport and clear touch mode. Omit device on a later
|
|
312
|
+
* block = **keep**; only hideDevice / clearDevice / device("desktop") clears.
|
|
313
|
+
*/
|
|
314
|
+
hideDevice(): void;
|
|
315
|
+
/** Alias for {@link StubCtx.hideDevice}. */
|
|
316
|
+
clearDevice(): void;
|
|
241
317
|
/** Default camera zoom for rings without their own zoom. */
|
|
242
318
|
zoom(n: number): void;
|
|
243
319
|
/**
|
|
@@ -280,6 +356,15 @@ export type StubPhaseResult = {
|
|
|
280
356
|
* - `keep` - author omitted todos; demo must leave previous dock alone
|
|
281
357
|
*/
|
|
282
358
|
todoSync?: "set" | "clear" | "keep";
|
|
359
|
+
/** Resolved device after this phase (undefined when keep with no prior). */
|
|
360
|
+
device?: DeviceState;
|
|
361
|
+
/**
|
|
362
|
+
* Device intent (mirror todoSync):
|
|
363
|
+
* - `set` - apply {@link device}
|
|
364
|
+
* - `clear` - reset to desktop (hideDevice / clearDevice / device desktop)
|
|
365
|
+
* - `keep` - author omitted device; demo must leave previous alone
|
|
366
|
+
*/
|
|
367
|
+
deviceSync?: "set" | "clear" | "keep";
|
|
283
368
|
zoom?: number;
|
|
284
369
|
zoomOut?: boolean;
|
|
285
370
|
title?: string;
|
|
@@ -426,7 +511,8 @@ export declare function normalizeTodoPos(raw: unknown): "left" | "right" | undef
|
|
|
426
511
|
export declare function normalizeTodoStyle(raw: unknown): TodoListStyle | undefined;
|
|
427
512
|
/**
|
|
428
513
|
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
429
|
-
* In checklist mode, todoIndex is ignored unless items omit explicit done
|
|
514
|
+
* In checklist / bullets mode, todoIndex is ignored unless items omit explicit done
|
|
515
|
+
* (bullets never auto-mark current).
|
|
430
516
|
*/
|
|
431
517
|
export declare function normalizeTodos(todos: readonly WaygraphTodoInput[] | undefined, todoIndex?: number, style?: TodoListStyle): WaygraphTodoItem[];
|
|
432
518
|
/** Build the floating dock from bag / phase fields. */
|
|
@@ -456,13 +542,13 @@ export declare function applyTodoPhase(prev: TodoDockState | undefined, phase: {
|
|
|
456
542
|
sync: "set" | "clear" | "keep";
|
|
457
543
|
};
|
|
458
544
|
/**
|
|
459
|
-
* Advance every sequential group to `index` (capped). Checklist
|
|
545
|
+
* Advance every sequential group to `index` (capped). Checklist / bullets unchanged.
|
|
460
546
|
* Used while cycling rings / fill/click stubs so the walkthrough moves.
|
|
461
547
|
*/
|
|
462
548
|
export declare function advanceTodoDock(dock: TodoDockState | undefined, index: number): TodoDockState | undefined;
|
|
463
549
|
/**
|
|
464
550
|
* Mark sequential groups complete (all done, none current) - e.g. end of step.
|
|
465
|
-
* Checklist groups keep explicit done flags.
|
|
551
|
+
* Checklist / bullets groups keep explicit done flags.
|
|
466
552
|
*/
|
|
467
553
|
export declare function completeSequentialTodoDock(dock: TodoDockState | undefined): TodoDockState | undefined;
|
|
468
554
|
/**
|
package/dist/highlights.js
CHANGED
|
@@ -12,6 +12,123 @@
|
|
|
12
12
|
* All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
|
|
13
13
|
* before advancing is authored the same way on stubs, flow fixtures, and slides.
|
|
14
14
|
*/
|
|
15
|
+
/** Built-in preset sizes (CSS pixels). */
|
|
16
|
+
export const DEVICE_PRESETS = {
|
|
17
|
+
mobile: {
|
|
18
|
+
preset: "mobile",
|
|
19
|
+
viewport: { width: 390, height: 844, deviceScaleFactor: 2 },
|
|
20
|
+
isMobile: true,
|
|
21
|
+
hasTouch: true,
|
|
22
|
+
touchMode: true,
|
|
23
|
+
},
|
|
24
|
+
tablet: {
|
|
25
|
+
preset: "tablet",
|
|
26
|
+
viewport: { width: 768, height: 1024, deviceScaleFactor: 2 },
|
|
27
|
+
isMobile: true,
|
|
28
|
+
hasTouch: true,
|
|
29
|
+
touchMode: true,
|
|
30
|
+
},
|
|
31
|
+
desktop: {
|
|
32
|
+
preset: "desktop",
|
|
33
|
+
viewport: { width: 1280, height: 720, deviceScaleFactor: 1 },
|
|
34
|
+
isMobile: false,
|
|
35
|
+
hasTouch: false,
|
|
36
|
+
touchMode: false,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export function normalizeDevicePreset(raw) {
|
|
40
|
+
const s = String(raw ?? "")
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
if (s === "mobile" || s === "phone" || s === "m")
|
|
44
|
+
return "mobile";
|
|
45
|
+
if (s === "tablet" || s === "pad" || s === "t")
|
|
46
|
+
return "tablet";
|
|
47
|
+
if (s === "desktop" || s === "desk" || s === "d" || s === "main")
|
|
48
|
+
return "desktop";
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
52
|
+
export function resolveDeviceState(input, touchOverride) {
|
|
53
|
+
if (input === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (typeof input === "string") {
|
|
56
|
+
const p = normalizeDevicePreset(input);
|
|
57
|
+
if (!p)
|
|
58
|
+
return undefined;
|
|
59
|
+
const base = DEVICE_PRESETS[p];
|
|
60
|
+
const touchMode = touchOverride !== undefined ? !!touchOverride : !!base.touchMode;
|
|
61
|
+
return {
|
|
62
|
+
preset: base.preset,
|
|
63
|
+
viewport: { ...base.viewport },
|
|
64
|
+
isMobile: base.isMobile,
|
|
65
|
+
hasTouch: base.hasTouch || touchMode,
|
|
66
|
+
touchMode,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if ("width" in input && "height" in input && !("preset" in input)) {
|
|
70
|
+
const vp = input;
|
|
71
|
+
const touchMode = touchOverride !== undefined ? !!touchOverride : true;
|
|
72
|
+
return {
|
|
73
|
+
preset: "mobile",
|
|
74
|
+
viewport: {
|
|
75
|
+
width: Math.max(200, Math.floor(vp.width)),
|
|
76
|
+
height: Math.max(200, Math.floor(vp.height)),
|
|
77
|
+
...(vp.deviceScaleFactor !== undefined
|
|
78
|
+
? { deviceScaleFactor: vp.deviceScaleFactor }
|
|
79
|
+
: { deviceScaleFactor: 2 }),
|
|
80
|
+
},
|
|
81
|
+
isMobile: true,
|
|
82
|
+
hasTouch: true,
|
|
83
|
+
touchMode,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const d = input;
|
|
87
|
+
const preset = normalizeDevicePreset(d.preset) || "desktop";
|
|
88
|
+
const base = DEVICE_PRESETS[preset];
|
|
89
|
+
const touchMode = touchOverride !== undefined
|
|
90
|
+
? !!touchOverride
|
|
91
|
+
: d.touchMode !== undefined
|
|
92
|
+
? !!d.touchMode
|
|
93
|
+
: !!base.touchMode;
|
|
94
|
+
const viewport = d.viewport
|
|
95
|
+
? {
|
|
96
|
+
width: Math.max(200, Math.floor(d.viewport.width)),
|
|
97
|
+
height: Math.max(200, Math.floor(d.viewport.height)),
|
|
98
|
+
...(d.viewport.deviceScaleFactor !== undefined
|
|
99
|
+
? { deviceScaleFactor: d.viewport.deviceScaleFactor }
|
|
100
|
+
: base.viewport.deviceScaleFactor !== undefined
|
|
101
|
+
? { deviceScaleFactor: base.viewport.deviceScaleFactor }
|
|
102
|
+
: {}),
|
|
103
|
+
}
|
|
104
|
+
: { ...base.viewport };
|
|
105
|
+
return {
|
|
106
|
+
preset,
|
|
107
|
+
viewport,
|
|
108
|
+
isMobile: d.isMobile !== undefined ? !!d.isMobile : base.isMobile,
|
|
109
|
+
hasTouch: d.hasTouch !== undefined ? !!d.hasTouch : base.hasTouch || touchMode,
|
|
110
|
+
touchMode,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
115
|
+
* - clear: hideDevice / clearDevice / device("desktop") with clear intent
|
|
116
|
+
* - set: author set device/viewport/touch this phase
|
|
117
|
+
* - keep: omit - preserve previous (whole episode remain)
|
|
118
|
+
*/
|
|
119
|
+
export function applyDevicePhase(prev, phase) {
|
|
120
|
+
const sync = phase.deviceSync ||
|
|
121
|
+
(phase.device ? "set" : "keep");
|
|
122
|
+
if (sync === "clear") {
|
|
123
|
+
return { device: resolveDeviceState("desktop", false), sync: "clear" };
|
|
124
|
+
}
|
|
125
|
+
if (sync === "set") {
|
|
126
|
+
if (phase.device)
|
|
127
|
+
return { device: phase.device, sync: "set" };
|
|
128
|
+
return { device: resolveDeviceState("desktop", false), sync: "clear" };
|
|
129
|
+
}
|
|
130
|
+
return { device: prev, sync: "keep" };
|
|
131
|
+
}
|
|
15
132
|
/** Apply Flow defaults under per-slot authored values (slot wins when set). */
|
|
16
133
|
export function applyHighlightStyleDefaults(slot, defaults) {
|
|
17
134
|
const tone = slot.tone !== undefined && slot.tone !== ""
|
|
@@ -378,11 +495,20 @@ export function normalizeTodoStyle(raw) {
|
|
|
378
495
|
return "sequential";
|
|
379
496
|
if (s === "checklist" || s === "check" || s === "checks" || s === "box")
|
|
380
497
|
return "checklist";
|
|
498
|
+
if (s === "bullets" ||
|
|
499
|
+
s === "bullet" ||
|
|
500
|
+
s === "list" ||
|
|
501
|
+
s === "ul" ||
|
|
502
|
+
s === "points" ||
|
|
503
|
+
s === "plain") {
|
|
504
|
+
return "bullets";
|
|
505
|
+
}
|
|
381
506
|
return undefined;
|
|
382
507
|
}
|
|
383
508
|
/**
|
|
384
509
|
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
385
|
-
* In checklist mode, todoIndex is ignored unless items omit explicit done
|
|
510
|
+
* In checklist / bullets mode, todoIndex is ignored unless items omit explicit done
|
|
511
|
+
* (bullets never auto-mark current).
|
|
386
512
|
*/
|
|
387
513
|
export function normalizeTodos(todos, todoIndex, style = "sequential") {
|
|
388
514
|
if (!todos || todos.length === 0)
|
|
@@ -406,6 +532,11 @@ export function normalizeTodos(todos, todoIndex, style = "sequential") {
|
|
|
406
532
|
item.current = false;
|
|
407
533
|
return item;
|
|
408
534
|
}
|
|
535
|
+
if (style === "bullets") {
|
|
536
|
+
// Flat list: no walkthrough current; keep explicit done if authored.
|
|
537
|
+
item.current = false;
|
|
538
|
+
return item;
|
|
539
|
+
}
|
|
409
540
|
if (idx !== undefined) {
|
|
410
541
|
if (item.done === undefined)
|
|
411
542
|
item.done = i < idx;
|
|
@@ -489,7 +620,7 @@ export function applyTodoPhase(prev, phase) {
|
|
|
489
620
|
return { dock: prev, sync: "keep" };
|
|
490
621
|
}
|
|
491
622
|
/**
|
|
492
|
-
* Advance every sequential group to `index` (capped). Checklist
|
|
623
|
+
* Advance every sequential group to `index` (capped). Checklist / bullets unchanged.
|
|
493
624
|
* Used while cycling rings / fill/click stubs so the walkthrough moves.
|
|
494
625
|
*/
|
|
495
626
|
export function advanceTodoDock(dock, index) {
|
|
@@ -519,7 +650,7 @@ export function advanceTodoDock(dock, index) {
|
|
|
519
650
|
}
|
|
520
651
|
/**
|
|
521
652
|
* Mark sequential groups complete (all done, none current) - e.g. end of step.
|
|
522
|
-
* Checklist groups keep explicit done flags.
|
|
653
|
+
* Checklist / bullets groups keep explicit done flags.
|
|
523
654
|
*/
|
|
524
655
|
export function completeSequentialTodoDock(dock) {
|
|
525
656
|
if (!dock)
|
|
@@ -600,6 +731,57 @@ function createStubCtx(bag, opts) {
|
|
|
600
731
|
bag.todos = [];
|
|
601
732
|
delete bag.todoGroups;
|
|
602
733
|
},
|
|
734
|
+
device(preset) {
|
|
735
|
+
bag.deviceTouched = true;
|
|
736
|
+
bag.hideDevice = false;
|
|
737
|
+
const resolved = resolveDeviceState(preset, bag.touchOverride);
|
|
738
|
+
if (resolved) {
|
|
739
|
+
bag.device = resolved;
|
|
740
|
+
if (resolved.preset === "desktop" && !resolved.touchMode) {
|
|
741
|
+
bag.hideDevice = true;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
viewport(box) {
|
|
746
|
+
bag.deviceTouched = true;
|
|
747
|
+
bag.hideDevice = false;
|
|
748
|
+
const resolved = resolveDeviceState(box, bag.touchOverride);
|
|
749
|
+
if (resolved)
|
|
750
|
+
bag.device = resolved;
|
|
751
|
+
},
|
|
752
|
+
touch(on) {
|
|
753
|
+
bag.deviceTouched = true;
|
|
754
|
+
const enabled = on === undefined ? true : !!on;
|
|
755
|
+
bag.touchOverride = enabled;
|
|
756
|
+
if (enabled)
|
|
757
|
+
bag.hideDevice = false;
|
|
758
|
+
if (bag.device) {
|
|
759
|
+
bag.device = {
|
|
760
|
+
...bag.device,
|
|
761
|
+
touchMode: enabled,
|
|
762
|
+
hasTouch: bag.device.hasTouch || enabled,
|
|
763
|
+
};
|
|
764
|
+
if (enabled)
|
|
765
|
+
bag.hideDevice = false;
|
|
766
|
+
}
|
|
767
|
+
else if (enabled) {
|
|
768
|
+
const base = resolveDeviceState("desktop", true);
|
|
769
|
+
if (base)
|
|
770
|
+
bag.device = { ...base, touchMode: true, hasTouch: true };
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
hideDevice() {
|
|
774
|
+
bag.deviceTouched = true;
|
|
775
|
+
bag.hideDevice = true;
|
|
776
|
+
bag.device = resolveDeviceState("desktop", false);
|
|
777
|
+
bag.touchOverride = false;
|
|
778
|
+
},
|
|
779
|
+
clearDevice() {
|
|
780
|
+
bag.deviceTouched = true;
|
|
781
|
+
bag.hideDevice = true;
|
|
782
|
+
bag.device = resolveDeviceState("desktop", false);
|
|
783
|
+
bag.touchOverride = false;
|
|
784
|
+
},
|
|
603
785
|
zoom(n) {
|
|
604
786
|
if (Number.isFinite(n) && n > 0)
|
|
605
787
|
bag.zoom = n;
|
|
@@ -648,6 +830,27 @@ function createStubCtx(bag, opts) {
|
|
|
648
830
|
bag.hideTodos = !partial.todoGroups || !partial.todoGroups.length;
|
|
649
831
|
bag.todoGroups = partial.todoGroups;
|
|
650
832
|
}
|
|
833
|
+
if (partial.touch !== undefined) {
|
|
834
|
+
bag.deviceTouched = true;
|
|
835
|
+
bag.touchOverride = !!partial.touch;
|
|
836
|
+
}
|
|
837
|
+
if (partial.device !== undefined) {
|
|
838
|
+
bag.deviceTouched = true;
|
|
839
|
+
bag.hideDevice = false;
|
|
840
|
+
const resolved = resolveDeviceState(partial.device, bag.touchOverride);
|
|
841
|
+
if (resolved) {
|
|
842
|
+
bag.device = resolved;
|
|
843
|
+
if (resolved.preset === "desktop" && !resolved.touchMode)
|
|
844
|
+
bag.hideDevice = true;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (partial.viewport !== undefined) {
|
|
848
|
+
bag.deviceTouched = true;
|
|
849
|
+
bag.hideDevice = false;
|
|
850
|
+
const resolved = resolveDeviceState(partial.viewport, bag.touchOverride);
|
|
851
|
+
if (resolved)
|
|
852
|
+
bag.device = resolved;
|
|
853
|
+
}
|
|
651
854
|
if (partial.zoom !== undefined && Number.isFinite(partial.zoom) && partial.zoom > 0) {
|
|
652
855
|
bag.zoom = partial.zoom;
|
|
653
856
|
}
|
|
@@ -812,12 +1015,37 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
812
1015
|
: bag.todoTouched && !todoDock
|
|
813
1016
|
? "clear"
|
|
814
1017
|
: "keep";
|
|
1018
|
+
let deviceState;
|
|
1019
|
+
if (bag.hideDevice) {
|
|
1020
|
+
deviceState = resolveDeviceState("desktop", false);
|
|
1021
|
+
}
|
|
1022
|
+
else if (bag.device) {
|
|
1023
|
+
deviceState = bag.touchOverride !== undefined
|
|
1024
|
+
? {
|
|
1025
|
+
...bag.device,
|
|
1026
|
+
touchMode: bag.touchOverride,
|
|
1027
|
+
hasTouch: bag.device.hasTouch || bag.touchOverride,
|
|
1028
|
+
}
|
|
1029
|
+
: bag.device;
|
|
1030
|
+
}
|
|
1031
|
+
else if (bag.touchOverride !== undefined) {
|
|
1032
|
+
deviceState = resolveDeviceState("desktop", bag.touchOverride);
|
|
1033
|
+
}
|
|
1034
|
+
const deviceSync = bag.hideDevice
|
|
1035
|
+
? "clear"
|
|
1036
|
+
: bag.deviceTouched && deviceState
|
|
1037
|
+
? "set"
|
|
1038
|
+
: bag.deviceTouched && !deviceState
|
|
1039
|
+
? "clear"
|
|
1040
|
+
: "keep";
|
|
815
1041
|
return {
|
|
816
1042
|
highlights,
|
|
817
1043
|
todos,
|
|
818
1044
|
todoSync,
|
|
1045
|
+
deviceSync,
|
|
819
1046
|
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
820
1047
|
...(todoDock ? { todoDock } : {}),
|
|
1048
|
+
...(deviceState ? { device: deviceState } : {}),
|
|
821
1049
|
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
822
1050
|
...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
|
|
823
1051
|
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
-
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, } from "./highlights.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, } from "./highlights.js";
|
|
8
8
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
10
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { connect, checkpoint } from "./types.js";
|
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
7
7
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
8
8
|
export { EdgeLeaseCoordinator } from "./traverse-lease.js";
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -37,6 +37,8 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
37
37
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
38
38
|
stubBefore: (ctx: StubCtx) => {
|
|
39
39
|
ctx.title("Pick a product");
|
|
40
|
+
// Back to desktop after mobile login (explicit clear - not omit/remain).
|
|
41
|
+
ctx.clearDevice();
|
|
40
42
|
ctx.todoId("saucedemo-cart");
|
|
41
43
|
ctx.todos([
|
|
42
44
|
{ id: "cart-find", text: "Find the product" },
|
|
@@ -51,11 +51,13 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
51
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
52
52
|
stubBefore: (ctx: StubCtx) => {
|
|
53
53
|
ctx.title("Signing in");
|
|
54
|
+
// 0.13+: mobile viewport + touch theater; omit on later blocks = remain.
|
|
55
|
+
ctx.device("mobile");
|
|
54
56
|
ctx.todoId("saucedemo-login");
|
|
55
57
|
ctx.todos([
|
|
56
58
|
{ id: "login-user", text: "Enter username" },
|
|
57
59
|
{ id: "login-pass", text: "Enter password" },
|
|
58
|
-
{ id: "login-submit", text: "
|
|
60
|
+
{ id: "login-submit", text: "Tap Login" },
|
|
59
61
|
]);
|
|
60
62
|
ctx.todoIndex(0);
|
|
61
63
|
ctx.zoom(1.35);
|
|
@@ -81,6 +83,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
81
83
|
color: "#86efac",
|
|
82
84
|
zoom: 1.55,
|
|
83
85
|
weight: "bold",
|
|
86
|
+
gesture: "tap",
|
|
84
87
|
},
|
|
85
88
|
});
|
|
86
89
|
},
|
package/package.json
CHANGED
package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -36,6 +36,8 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
36
36
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
37
37
|
stubBefore: (ctx) => {
|
|
38
38
|
ctx.title("Pick a product");
|
|
39
|
+
// Back to desktop after mobile login (explicit clear - not omit/remain).
|
|
40
|
+
ctx.clearDevice();
|
|
39
41
|
ctx.todoId("saucedemo-cart");
|
|
40
42
|
ctx.todos([
|
|
41
43
|
{ id: "cart-find", text: "Find the product" },
|
|
@@ -51,11 +51,13 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
51
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
52
52
|
stubBefore: (ctx: StubCtx) => {
|
|
53
53
|
ctx.title("Signing in");
|
|
54
|
+
// 0.13+: mobile viewport + touch theater; omit on later blocks = remain.
|
|
55
|
+
ctx.device("mobile");
|
|
54
56
|
ctx.todoId("saucedemo-login");
|
|
55
57
|
ctx.todos([
|
|
56
58
|
{ id: "login-user", text: "Enter username" },
|
|
57
59
|
{ id: "login-pass", text: "Enter password" },
|
|
58
|
-
{ id: "login-submit", text: "
|
|
60
|
+
{ id: "login-submit", text: "Tap Login" },
|
|
59
61
|
]);
|
|
60
62
|
ctx.todoIndex(0);
|
|
61
63
|
ctx.zoom(1.35);
|
|
@@ -81,6 +83,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
81
83
|
color: "#86efac",
|
|
82
84
|
zoom: 1.55,
|
|
83
85
|
weight: "bold",
|
|
86
|
+
gesture: "tap",
|
|
84
87
|
},
|
|
85
88
|
});
|
|
86
89
|
},
|