executable-stories-showcase 0.1.0 → 0.3.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/playwright.d.ts +29 -2
- package/dist/playwright.js +112 -24
- package/dist/playwright.js.map +1 -1
- package/package.json +1 -1
package/dist/playwright.d.ts
CHANGED
|
@@ -24,8 +24,35 @@ declare global {
|
|
|
24
24
|
}
|
|
25
25
|
/** Requests the active scenario didn't cover, e.g. to assert none in strict runs. */
|
|
26
26
|
export declare const unhandledRequests: (page: Page) => Promise<string[]>;
|
|
27
|
+
export interface CursorOptions {
|
|
28
|
+
/** `'arrow'` (default), `'dot'`, or your own SVG/HTML markup, drawn with its top-left corner on the mouse. */
|
|
29
|
+
pointer?: 'arrow' | 'dot' | (string & {});
|
|
30
|
+
/** Any CSS color for the pointer, its pulsing halo and the click ripple. Default `#ff3d71`. */
|
|
31
|
+
color?: string;
|
|
32
|
+
/** Scale for the pointer: 1 is a browser-sized 24px arrow, 1.5 half again. Default 1. */
|
|
33
|
+
size?: number;
|
|
34
|
+
/** Diameter of the pulsing halo in pixels, or `false` for none. Default 36. */
|
|
35
|
+
halo?: false | number;
|
|
36
|
+
/**
|
|
37
|
+
* Let the cursor arrive before each action lands, for videos: clicks, checks and typing
|
|
38
|
+
* (`fill`, `pressSequentially`, `selectOption`) wait for the glide first. `true` waits 400ms,
|
|
39
|
+
* a number that many ms. See {@link glideBeforeActions}.
|
|
40
|
+
*/
|
|
41
|
+
glide?: boolean | number;
|
|
42
|
+
}
|
|
27
43
|
/**
|
|
28
44
|
* Draw a mouse pointer that follows Playwright's mouse, since recorded videos don't show one.
|
|
29
|
-
* It
|
|
45
|
+
* It pulses so the eye finds it, glides between points, follows focus (so `fill()` steps show where
|
|
46
|
+
* typing lands), and drops a ripple on each click. One pointer is drawn in the top frame and
|
|
47
|
+
* follows the mouse into iframes. Call before the page loads, e.g. before `openScenario`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function showCursor(page: Page, { pointer, color, size, halo, glide }?: CursorOptions): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Make locator actions on this page move the mouse to their target and wait for the drawn
|
|
52
|
+
* cursor's glide before they land, so videos show the pointer arriving first. Playwright's
|
|
53
|
+
* click moves and presses in one go, and `fill` types without moving the mouse at all.
|
|
54
|
+
* Covers click, dblclick, check, uncheck, fill, pressSequentially and selectOption. The Locator
|
|
55
|
+
* prototype is shared, so it's patched once and other pages act as usual.
|
|
56
|
+
* `showCursor(page, { glide: true })` calls this for you.
|
|
30
57
|
*/
|
|
31
|
-
export declare function
|
|
58
|
+
export declare function glideBeforeActions(page: Page, ms?: number): void;
|
package/dist/playwright.js
CHANGED
|
@@ -54,36 +54,124 @@ async function setPanel(page, show) {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
var unhandledRequests = (page) => page.evaluate(() => globalThis.executableStoriesShowcaseUnhandled ?? []);
|
|
57
|
-
async function showCursor(page) {
|
|
58
|
-
|
|
57
|
+
async function showCursor(page, { pointer = "arrow", color = "#ff3d71", size = 1, halo = 36, glide = false } = {}) {
|
|
58
|
+
if (glide !== false) glideBeforeActions(page, glide === true ? void 0 : glide);
|
|
59
|
+
await page.addInitScript(({ pointer: pointer2, color: color2, size: size2, halo: halo2 }) => {
|
|
60
|
+
let show = (p) => parent.postMessage({ executableStoriesShowcaseCursor: p }, "*");
|
|
61
|
+
let clicking = false;
|
|
62
|
+
addEventListener("mousemove", (e) => show({ type: "move", x: e.clientX, y: e.clientY }), true);
|
|
63
|
+
addEventListener("mousedown", (e) => {
|
|
64
|
+
clicking = true;
|
|
65
|
+
show({ type: "down", x: e.clientX, y: e.clientY });
|
|
66
|
+
}, true);
|
|
67
|
+
addEventListener("mouseup", (e) => {
|
|
68
|
+
show({ type: "up", x: e.clientX, y: e.clientY });
|
|
69
|
+
setTimeout(() => clicking = false);
|
|
70
|
+
}, true);
|
|
71
|
+
addEventListener("keydown", () => clicking = false, true);
|
|
72
|
+
addEventListener("focusin", (e) => {
|
|
73
|
+
if (clicking || !(e.target instanceof Element) || e.target instanceof HTMLIFrameElement) return;
|
|
74
|
+
const r = e.target.getBoundingClientRect();
|
|
75
|
+
show({ type: "move", x: r.left + Math.min(24, r.width / 2), y: r.top + r.height / 2 });
|
|
76
|
+
}, true);
|
|
77
|
+
addEventListener("message", (e) => {
|
|
78
|
+
const p = e.data?.executableStoriesShowcaseCursor;
|
|
79
|
+
const frame = [...document.querySelectorAll("iframe")].find((f) => f.contentWindow === e.source);
|
|
80
|
+
if (!p || !frame) return;
|
|
81
|
+
const box = frame.getBoundingClientRect();
|
|
82
|
+
const style2 = getComputedStyle(frame);
|
|
83
|
+
const scaleX = box.width / frame.offsetWidth || 1;
|
|
84
|
+
const scaleY = box.height / frame.offsetHeight || 1;
|
|
85
|
+
show({
|
|
86
|
+
...p,
|
|
87
|
+
x: box.left + (frame.clientLeft + parseFloat(style2.paddingLeft) + p.x) * scaleX,
|
|
88
|
+
y: box.top + (frame.clientTop + parseFloat(style2.paddingTop) + p.y) * scaleY
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
if (window !== window.top) return;
|
|
92
|
+
const style = document.createElement("style");
|
|
93
|
+
style.textContent = `
|
|
94
|
+
.es-cursor, .es-ripple { position: fixed; left: 0; top: 0; pointer-events: none; z-index: 2147483647; }
|
|
95
|
+
.es-cursor { transition: transform .35s cubic-bezier(.2,.7,.3,1); }
|
|
96
|
+
.es-cursor > * { position: absolute; left: 0; top: 0; }
|
|
97
|
+
.es-cursor > :last-child { transition: scale .1s; transform-origin: 0 0; transform: scale(${size2}); }
|
|
98
|
+
.es-cursor[data-down] > :last-child { scale: .8; }
|
|
99
|
+
.es-halo {
|
|
100
|
+
left: ${-halo2 / 2}px; top: ${-halo2 / 2}px; width: ${halo2}px; height: ${halo2}px; border-radius: 50%; background: ${color2};
|
|
101
|
+
animation: es-cursor-halo 1.2s ease-out infinite;
|
|
102
|
+
}
|
|
103
|
+
.es-ripple {
|
|
104
|
+
width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%; box-sizing: border-box;
|
|
105
|
+
border: 4px solid ${color2}; animation: es-cursor-ripple .6s ease-out forwards;
|
|
106
|
+
}
|
|
107
|
+
@keyframes es-cursor-halo { from { opacity: .8; scale: .4 } to { opacity: 0; scale: 1.6 } }
|
|
108
|
+
@keyframes es-cursor-ripple { from { opacity: .9; scale: .3 } to { opacity: 0; scale: 2.6 } }`;
|
|
109
|
+
const shapes = {
|
|
110
|
+
arrow: `<svg width="24" height="28" viewBox="-1 -1 18 26" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))"><path d="M0 0V20L5.5 15 9 23 12 21.5 8.6 14H16Z" fill="${color2}" stroke="#fff" stroke-width="1.5" stroke-linejoin="round"/></svg>`,
|
|
111
|
+
dot: `<div style="width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;background:${color2};border:3px solid #fff;box-sizing:border-box"></div>`
|
|
112
|
+
};
|
|
59
113
|
const cursor = document.createElement("div");
|
|
114
|
+
cursor.className = "es-cursor";
|
|
60
115
|
cursor.setAttribute("aria-hidden", "true");
|
|
61
|
-
cursor.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
addEventListener("mouseup", () => cursor.style.scale = "1", true);
|
|
81
|
-
const attach = () => document.documentElement.append(cursor);
|
|
116
|
+
cursor.innerHTML = `${halo2 ? '<div class="es-halo"></div>' : ""}<div>${shapes[pointer2] ?? pointer2}</div>`;
|
|
117
|
+
cursor.style.transform = `translate(${innerWidth / 2}px,${innerHeight / 2}px)`;
|
|
118
|
+
show = ({ type, x, y }) => {
|
|
119
|
+
if (type === "up") {
|
|
120
|
+
delete cursor.dataset.down;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
cursor.style.transform = `translate(${x}px,${y}px)`;
|
|
124
|
+
if (type === "move") return;
|
|
125
|
+
cursor.dataset.down = "";
|
|
126
|
+
const ripple = document.createElement("div");
|
|
127
|
+
ripple.className = "es-ripple";
|
|
128
|
+
ripple.setAttribute("aria-hidden", "true");
|
|
129
|
+
ripple.style.left = `${x}px`;
|
|
130
|
+
ripple.style.top = `${y}px`;
|
|
131
|
+
ripple.addEventListener("animationend", () => ripple.remove());
|
|
132
|
+
document.documentElement.append(ripple);
|
|
133
|
+
};
|
|
134
|
+
const attach = () => document.documentElement.append(style, cursor);
|
|
82
135
|
if (document.documentElement) attach();
|
|
83
136
|
else addEventListener("DOMContentLoaded", attach);
|
|
84
|
-
});
|
|
137
|
+
}, { pointer, color, size, halo: halo || 0 });
|
|
138
|
+
}
|
|
139
|
+
var gliding = /* @__PURE__ */ new WeakMap();
|
|
140
|
+
var POINTED = ["click", "dblclick", "check", "uncheck"];
|
|
141
|
+
var TYPED = ["fill", "pressSequentially", "selectOption"];
|
|
142
|
+
function glideBeforeActions(page, ms = 400) {
|
|
143
|
+
gliding.set(page, ms);
|
|
144
|
+
const proto = Object.getPrototypeOf(page.locator("html"));
|
|
145
|
+
if (proto.glides) return;
|
|
146
|
+
proto.glides = true;
|
|
147
|
+
for (const name of [...POINTED, ...TYPED]) {
|
|
148
|
+
const action = proto[name];
|
|
149
|
+
const pointed = POINTED.includes(name);
|
|
150
|
+
proto[name] = async function(...args) {
|
|
151
|
+
const wait = gliding.get(this.page());
|
|
152
|
+
const options = pointed ? args[0] : args[1];
|
|
153
|
+
if (wait !== void 0) await glideTo(this, wait, pointed, options);
|
|
154
|
+
return action.apply(this, args);
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function glideTo(locator, wait, pointed, options = {}) {
|
|
159
|
+
for (let tries = 0; tries < 3; tries++) {
|
|
160
|
+
if (pointed) {
|
|
161
|
+
await locator.hover({ position: options.position, force: options.force, timeout: options.timeout });
|
|
162
|
+
} else {
|
|
163
|
+
const box = await locator.boundingBox({ timeout: options.timeout });
|
|
164
|
+
if (!box) return;
|
|
165
|
+
await locator.page().mouse.move(box.x + Math.min(24, box.width / 2), box.y + box.height / 2);
|
|
166
|
+
}
|
|
167
|
+
const before = await locator.boundingBox();
|
|
168
|
+
await locator.page().waitForTimeout(wait);
|
|
169
|
+
const after = await locator.boundingBox();
|
|
170
|
+
if (JSON.stringify(before) === JSON.stringify(after)) break;
|
|
171
|
+
}
|
|
85
172
|
}
|
|
86
173
|
export {
|
|
174
|
+
glideBeforeActions,
|
|
87
175
|
openInlineScenario,
|
|
88
176
|
openScenario,
|
|
89
177
|
showCursor,
|
package/dist/playwright.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/playwright.ts"],"sourcesContent":["import type { Page } from '@playwright/test';\nimport { DATA_KEY, PARAM, STRICT_PARAM } from './link.js';\nimport type { ScenarioInput } from './schema.js';\n\nexport interface OpenOptions {\n /** App path to open. Default \"/\". */\n path?: string;\n /** Fail unmocked requests instead of letting them reach a real backend. Default true. */\n strict?: boolean;\n /** Show the dev panel. False hides it, for screenshots that go into product docs. Default true. */\n panel?: boolean;\n}\n\n/**\n * Open the app in a catalog scenario and wait until the UI shows it: the scenario is\n * active and its `ready` selector, if any, is visible.\n */\nexport async function openScenario(page: Page, name: string, options: OpenOptions = {}) {\n await open(page, name, new URLSearchParams({ [PARAM]: name }), '', options);\n}\n\n/**\n * Like `openScenario`, for a scenario defined in the test rather than the catalog.\n * It opens as an inline link, so off localhost the app needs `startScenarios({ allowLinks: true })`.\n */\nexport async function openInlineScenario(page: Page, scenario: ScenarioInput, options: OpenOptions = {}) {\n const hash = `#${new URLSearchParams({ [DATA_KEY]: JSON.stringify(scenario) })}`;\n await open(page, scenario.name, new URLSearchParams(), hash, { path: scenario.path, ...options });\n}\n\nasync function open(\n page: Page,\n name: string,\n search: URLSearchParams,\n hash: string,\n { path = '/', strict = true, panel = true }: OpenOptions,\n) {\n // Relative to the test's baseURL; `//host`, `/\\host` or a full URL would open another site.\n if (!/^\\/(?![/\\\\])/.test(path)) {\n throw new Error(`[executable-stories-showcase] path \"${path}\" must be a path on the app, such as /orders/42`);\n }\n\n if (strict) search.set(STRICT_PARAM, '');\n await setPanel(page, panel);\n // Leave the page first: a hash-only change would not reload the app.\n await page.goto('about:blank');\n await page.goto(`${path}?${search}${hash}`);\n const html = page.locator(`html[data-scenario=\"${name}\"]`);\n await html.waitFor({ state: 'attached' });\n const ready = await html.getAttribute('data-scenario-ready');\n\n if (ready) await page.locator(ready).first().waitFor();\n}\n\ndeclare global {\n /** Exposed by `openScenario({ panel: false })`: whether this page should hide the panel. */\n var executableStoriesShowcasePanelHidden: (() => Promise<boolean>) | undefined;\n}\n\n// Per page: whether the panel is hidden. The init script asks on every load, so a later\n// open can show the panel again on any Playwright version.\nconst panelHidden = new WeakMap<Page, { hidden: boolean; }>();\n\nasync function setPanel(page: Page, show: boolean) {\n const state = panelHidden.get(page);\n\n if (state) {\n state.hidden = !show;\n\n return;\n }\n\n if (show) return;\n const created = { hidden: true };\n panelHidden.set(page, created);\n await page.exposeFunction('executableStoriesShowcasePanelHidden', () => created.hidden);\n // Hide before the app's scripts run, so the panel never shows, not even in a video;\n // then show it again if this page no longer wants it hidden.\n await page.addInitScript(() => {\n const style = document.createElement('style');\n style.textContent = 'scenario-panel { display: none !important; }';\n // Init scripts can run before <html> exists: attach as soon as it does.\n const attach = () => document.documentElement.append(style);\n\n if (document.documentElement) attach();\n else {\n new MutationObserver((_, observer) => {\n if (!document.documentElement) return;\n observer.disconnect();\n attach();\n }).observe(document, { childList: true });\n }\n\n void globalThis.executableStoriesShowcasePanelHidden?.().then((hidden) => {\n if (!hidden) style.remove();\n });\n });\n}\n\n/** Requests the active scenario didn't cover, e.g. to assert none in strict runs. */\nexport const unhandledRequests = (page: Page): Promise<string[]> =>\n page.evaluate(() => globalThis.executableStoriesShowcaseUnhandled ?? []);\n\n/**\n * Draw a mouse pointer that follows Playwright's mouse, since recorded videos don't show one.\n * It glides between points and pulses on click. Call before the page loads, e.g. before `openScenario`.\n */\nexport async function showCursor(page: Page) {\n await page.addInitScript(() => {\n const cursor = document.createElement('div');\n cursor.setAttribute('aria-hidden', 'true');\n cursor.style.cssText = [\n 'position:fixed',\n 'left:0',\n 'top:0',\n 'width:18px',\n 'height:18px',\n 'margin:-9px 0 0 -9px',\n 'border-radius:50%',\n 'background:rgba(20,20,20,.55)',\n 'border:2px solid #fff',\n 'box-shadow:0 1px 4px rgba(0,0,0,.4)',\n 'pointer-events:none',\n 'z-index:2147483647',\n 'transform:translate(-40px,-40px)',\n 'transition:transform .25s ease-out,scale .1s',\n ].join(';');\n\n const place = (e: MouseEvent) => (cursor.style.transform = `translate(${e.clientX}px,${e.clientY}px)`);\n addEventListener('mousemove', place, true);\n addEventListener('mousedown', () => (cursor.style.scale = '0.7'), true);\n addEventListener('mouseup', () => (cursor.style.scale = '1'), true);\n\n const attach = () => document.documentElement.append(cursor);\n\n if (document.documentElement) attach();\n else addEventListener('DOMContentLoaded', attach);\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/playwright.ts"],"sourcesContent":["import type { Locator, Page } from '@playwright/test';\nimport { DATA_KEY, PARAM, STRICT_PARAM } from './link.js';\nimport type { ScenarioInput } from './schema.js';\n\nexport interface OpenOptions {\n /** App path to open. Default \"/\". */\n path?: string;\n /** Fail unmocked requests instead of letting them reach a real backend. Default true. */\n strict?: boolean;\n /** Show the dev panel. False hides it, for screenshots that go into product docs. Default true. */\n panel?: boolean;\n}\n\n/**\n * Open the app in a catalog scenario and wait until the UI shows it: the scenario is\n * active and its `ready` selector, if any, is visible.\n */\nexport async function openScenario(page: Page, name: string, options: OpenOptions = {}) {\n await open(page, name, new URLSearchParams({ [PARAM]: name }), '', options);\n}\n\n/**\n * Like `openScenario`, for a scenario defined in the test rather than the catalog.\n * It opens as an inline link, so off localhost the app needs `startScenarios({ allowLinks: true })`.\n */\nexport async function openInlineScenario(page: Page, scenario: ScenarioInput, options: OpenOptions = {}) {\n const hash = `#${new URLSearchParams({ [DATA_KEY]: JSON.stringify(scenario) })}`;\n await open(page, scenario.name, new URLSearchParams(), hash, { path: scenario.path, ...options });\n}\n\nasync function open(\n page: Page,\n name: string,\n search: URLSearchParams,\n hash: string,\n { path = '/', strict = true, panel = true }: OpenOptions,\n) {\n // Relative to the test's baseURL; `//host`, `/\\host` or a full URL would open another site.\n if (!/^\\/(?![/\\\\])/.test(path)) {\n throw new Error(`[executable-stories-showcase] path \"${path}\" must be a path on the app, such as /orders/42`);\n }\n\n if (strict) search.set(STRICT_PARAM, '');\n await setPanel(page, panel);\n // Leave the page first: a hash-only change would not reload the app.\n await page.goto('about:blank');\n await page.goto(`${path}?${search}${hash}`);\n const html = page.locator(`html[data-scenario=\"${name}\"]`);\n await html.waitFor({ state: 'attached' });\n const ready = await html.getAttribute('data-scenario-ready');\n\n if (ready) await page.locator(ready).first().waitFor();\n}\n\ndeclare global {\n /** Exposed by `openScenario({ panel: false })`: whether this page should hide the panel. */\n var executableStoriesShowcasePanelHidden: (() => Promise<boolean>) | undefined;\n}\n\n// Per page: whether the panel is hidden. The init script asks on every load, so a later\n// open can show the panel again on any Playwright version.\nconst panelHidden = new WeakMap<Page, { hidden: boolean; }>();\n\nasync function setPanel(page: Page, show: boolean) {\n const state = panelHidden.get(page);\n\n if (state) {\n state.hidden = !show;\n\n return;\n }\n\n if (show) return;\n const created = { hidden: true };\n panelHidden.set(page, created);\n await page.exposeFunction('executableStoriesShowcasePanelHidden', () => created.hidden);\n // Hide before the app's scripts run, so the panel never shows, not even in a video;\n // then show it again if this page no longer wants it hidden.\n await page.addInitScript(() => {\n const style = document.createElement('style');\n style.textContent = 'scenario-panel { display: none !important; }';\n // Init scripts can run before <html> exists: attach as soon as it does.\n const attach = () => document.documentElement.append(style);\n\n if (document.documentElement) attach();\n else {\n new MutationObserver((_, observer) => {\n if (!document.documentElement) return;\n observer.disconnect();\n attach();\n }).observe(document, { childList: true });\n }\n\n void globalThis.executableStoriesShowcasePanelHidden?.().then((hidden) => {\n if (!hidden) style.remove();\n });\n });\n}\n\n/** Requests the active scenario didn't cover, e.g. to assert none in strict runs. */\nexport const unhandledRequests = (page: Page): Promise<string[]> =>\n page.evaluate(() => globalThis.executableStoriesShowcaseUnhandled ?? []);\n\nexport interface CursorOptions {\n /** `'arrow'` (default), `'dot'`, or your own SVG/HTML markup, drawn with its top-left corner on the mouse. */\n pointer?: 'arrow' | 'dot' | (string & {});\n /** Any CSS color for the pointer, its pulsing halo and the click ripple. Default `#ff3d71`. */\n color?: string;\n /** Scale for the pointer: 1 is a browser-sized 24px arrow, 1.5 half again. Default 1. */\n size?: number;\n /** Diameter of the pulsing halo in pixels, or `false` for none. Default 36. */\n halo?: false | number;\n /**\n * Let the cursor arrive before each action lands, for videos: clicks, checks and typing\n * (`fill`, `pressSequentially`, `selectOption`) wait for the glide first. `true` waits 400ms,\n * a number that many ms. See {@link glideBeforeActions}.\n */\n glide?: boolean | number;\n}\n\n/**\n * Draw a mouse pointer that follows Playwright's mouse, since recorded videos don't show one.\n * It pulses so the eye finds it, glides between points, follows focus (so `fill()` steps show where\n * typing lands), and drops a ripple on each click. One pointer is drawn in the top frame and\n * follows the mouse into iframes. Call before the page loads, e.g. before `openScenario`.\n */\nexport async function showCursor(\n page: Page,\n { pointer = 'arrow', color = '#ff3d71', size = 1, halo = 36, glide = false }: CursorOptions = {},\n) {\n if (glide !== false) glideBeforeActions(page, glide === true ? undefined : glide);\n await page.addInitScript(({ pointer, color, size, halo }) => {\n type Pointer = { type: 'move' | 'down' | 'up'; x: number; y: number; };\n\n // Only the top frame draws. Other frames pass their pointer to their parent, which adds the\n // iframe's place, so a page with iframes shows one cursor.\n let show = (p: Pointer) => parent.postMessage({ executableStoriesShowcaseCursor: p }, '*');\n\n // A click focuses on mousedown, or for a <label> in the click after mouseup; the mouse is already there.\n let clicking = false;\n\n addEventListener('mousemove', (e) => show({ type: 'move', x: e.clientX, y: e.clientY }), true);\n addEventListener('mousedown', (e) => {\n clicking = true;\n show({ type: 'down', x: e.clientX, y: e.clientY });\n }, true);\n addEventListener('mouseup', (e) => {\n show({ type: 'up', x: e.clientX, y: e.clientY });\n setTimeout(() => (clicking = false));\n }, true);\n addEventListener('keydown', () => (clicking = false), true);\n // fill() and keyboard steps focus a field without moving the mouse. An iframe gets focus when\n // something inside it does; that frame reports where.\n addEventListener('focusin', (e) => {\n if (clicking || !(e.target instanceof Element) || e.target instanceof HTMLIFrameElement) return;\n const r = e.target.getBoundingClientRect();\n show({ type: 'move', x: r.left + Math.min(24, r.width / 2), y: r.top + r.height / 2 });\n }, true);\n addEventListener('message', (e) => {\n const p: Pointer | undefined = e.data?.executableStoriesShowcaseCursor;\n const frame = [...document.querySelectorAll('iframe')].find((f) => f.contentWindow === e.source);\n\n if (!p || !frame) return;\n\n // The measured box over the layout size gives the iframe's CSS scale, which applies to its\n // border, padding and the point itself.\n const box = frame.getBoundingClientRect();\n const style = getComputedStyle(frame);\n const scaleX = box.width / frame.offsetWidth || 1;\n const scaleY = box.height / frame.offsetHeight || 1;\n\n show({\n ...p,\n x: box.left + (frame.clientLeft + parseFloat(style.paddingLeft) + p.x) * scaleX,\n y: box.top + (frame.clientTop + parseFloat(style.paddingTop) + p.y) * scaleY,\n });\n });\n\n if (window !== window.top) return;\n\n const style = document.createElement('style');\n style.textContent = `\n .es-cursor, .es-ripple { position: fixed; left: 0; top: 0; pointer-events: none; z-index: 2147483647; }\n .es-cursor { transition: transform .35s cubic-bezier(.2,.7,.3,1); }\n .es-cursor > * { position: absolute; left: 0; top: 0; }\n .es-cursor > :last-child { transition: scale .1s; transform-origin: 0 0; transform: scale(${size}); }\n .es-cursor[data-down] > :last-child { scale: .8; }\n .es-halo {\n left: ${-halo / 2}px; top: ${-halo / 2}px; width: ${halo}px; height: ${halo}px; border-radius: 50%; background: ${color};\n animation: es-cursor-halo 1.2s ease-out infinite;\n }\n .es-ripple {\n width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%; box-sizing: border-box;\n border: 4px solid ${color}; animation: es-cursor-ripple .6s ease-out forwards;\n }\n @keyframes es-cursor-halo { from { opacity: .8; scale: .4 } to { opacity: 0; scale: 1.6 } }\n @keyframes es-cursor-ripple { from { opacity: .9; scale: .3 } to { opacity: 0; scale: 2.6 } }`;\n\n const shapes: Record<string, string> = {\n arrow: `<svg width=\"24\" height=\"28\" viewBox=\"-1 -1 18 26\" style=\"display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))\">`\n + `<path d=\"M0 0V20L5.5 15 9 23 12 21.5 8.6 14H16Z\" fill=\"${color}\" stroke=\"#fff\" stroke-width=\"1.5\" stroke-linejoin=\"round\"/></svg>`,\n dot: `<div style=\"width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;background:${color};border:3px solid #fff;box-sizing:border-box\"></div>`,\n };\n\n // A zero-size box on the mouse: the halo centres on it, the pointer hangs from it.\n const cursor = document.createElement('div');\n cursor.className = 'es-cursor';\n cursor.setAttribute('aria-hidden', 'true');\n cursor.innerHTML = `${halo ? '<div class=\"es-halo\"></div>' : ''}<div>${shapes[pointer] ?? pointer}</div>`;\n // Start mid-screen so the first glide to a target shows in the video.\n cursor.style.transform = `translate(${innerWidth / 2}px,${innerHeight / 2}px)`;\n\n show = ({ type, x, y }) => {\n if (type === 'up') {\n delete cursor.dataset.down;\n\n return;\n }\n\n cursor.style.transform = `translate(${x}px,${y}px)`;\n\n if (type === 'move') return;\n\n cursor.dataset.down = '';\n const ripple = document.createElement('div');\n ripple.className = 'es-ripple';\n ripple.setAttribute('aria-hidden', 'true');\n ripple.style.left = `${x}px`;\n ripple.style.top = `${y}px`;\n ripple.addEventListener('animationend', () => ripple.remove());\n document.documentElement.append(ripple);\n };\n\n const attach = () => document.documentElement.append(style, cursor);\n\n if (document.documentElement) attach();\n else addEventListener('DOMContentLoaded', attach);\n }, { pointer, color, size, halo: halo || 0 });\n}\n\nconst gliding = new WeakMap<Page, number>();\n// Actions that press at a point take its `position`; the typing ones only need the field.\nconst POINTED = ['click', 'dblclick', 'check', 'uncheck'] as const;\nconst TYPED = ['fill', 'pressSequentially', 'selectOption'] as const;\n\ntype ActionOptions = { position?: { x: number; y: number; }; force?: boolean; timeout?: number; };\n\n/**\n * Make locator actions on this page move the mouse to their target and wait for the drawn\n * cursor's glide before they land, so videos show the pointer arriving first. Playwright's\n * click moves and presses in one go, and `fill` types without moving the mouse at all.\n * Covers click, dblclick, check, uncheck, fill, pressSequentially and selectOption. The Locator\n * prototype is shared, so it's patched once and other pages act as usual.\n * `showCursor(page, { glide: true })` calls this for you.\n */\nexport function glideBeforeActions(page: Page, ms = 400) {\n gliding.set(page, ms);\n\n const proto: Record<string, unknown> & { glides?: true; } = Object.getPrototypeOf(page.locator('html'));\n\n if (proto.glides) return;\n proto.glides = true;\n\n for (const name of [...POINTED, ...TYPED]) {\n const action = proto[name] as (this: Locator, ...args: unknown[]) => Promise<unknown>;\n const pointed = (POINTED as readonly string[]).includes(name);\n\n proto[name] = async function(this: Locator, ...args: unknown[]) {\n const wait = gliding.get(this.page());\n // click(options), fill(value, options): the options come after the value when there is one.\n const options = (pointed ? args[0] : args[1]) as ActionOptions | undefined;\n\n if (wait !== undefined) await glideTo(this, wait, pointed, options);\n\n return action.apply(this, args);\n };\n }\n}\n\nasync function glideTo(locator: Locator, wait: number, pointed: boolean, options: ActionOptions = {}) {\n // A target that moves or resizes during the glide (its point moves either way) gets\n // another one, so the cursor is there when the action lands.\n // Three tries at most; a target that never settles gets the action wherever it is.\n for (let tries = 0; tries < 3; tries++) {\n if (pointed) {\n await locator.hover({ position: options.position, force: options.force, timeout: options.timeout });\n } else {\n // No hover: a field can be typed into while something covers it, which hover would wait out.\n const box = await locator.boundingBox({ timeout: options.timeout });\n\n if (!box) return;\n // Where showCursor puts the pointer when the field takes focus.\n await locator.page().mouse.move(box.x + Math.min(24, box.width / 2), box.y + box.height / 2);\n }\n\n const before = await locator.boundingBox();\n await locator.page().waitForTimeout(wait);\n const after = await locator.boundingBox();\n\n if (JSON.stringify(before) === JSON.stringify(after)) break;\n }\n}\n"],"mappings":"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","names":["pointer","color","size","halo","style"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executable-stories-showcase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Put a running app into any state from a URL: shareable JSON scenarios for network, URL and app state, with a dev panel, Playwright helpers and adapters.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|