@skippr/live-agent-sdk 0.55.0 → 0.57.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/esm/lib-exports.js +207 -9
- package/dist/skippr-sdk.js +133 -133
- package/dist/types/components/AgentCursor.d.ts +6 -1
- package/package.json +1 -1
package/dist/esm/lib-exports.js
CHANGED
|
@@ -1210,9 +1210,17 @@ var SETTLE_TO_CENTER_MS = 420;
|
|
|
1210
1210
|
var ACT_CLICK_DELAY_MS = 400;
|
|
1211
1211
|
var BASIC_ACT_DELAY_MS = 150;
|
|
1212
1212
|
var TRACK_GLIDE_MS = 90;
|
|
1213
|
+
var GESTURE_GLIDE_MS = 500;
|
|
1214
|
+
var GESTURE_ACT_DELAY_MS = 200;
|
|
1213
1215
|
function cursorEntryPoint() {
|
|
1214
1216
|
return { x: window.innerWidth - 72, y: window.innerHeight - 80 };
|
|
1215
1217
|
}
|
|
1218
|
+
function gesturePoint(direction) {
|
|
1219
|
+
return {
|
|
1220
|
+
x: window.innerWidth / 2,
|
|
1221
|
+
y: direction === "down" ? window.innerHeight - 96 : 96
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1216
1224
|
function centerOf(rect) {
|
|
1217
1225
|
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
1218
1226
|
}
|
|
@@ -1295,7 +1303,18 @@ function AgentCursor({
|
|
|
1295
1303
|
useLayoutEffect(() => {
|
|
1296
1304
|
cancelAnimation();
|
|
1297
1305
|
setClickPulse(0);
|
|
1298
|
-
if (
|
|
1306
|
+
if (target?.kind === "gesture" && target.gesture) {
|
|
1307
|
+
setPhase("done");
|
|
1308
|
+
setRingBox(null);
|
|
1309
|
+
const destination = gesturePoint(target.gesture.direction);
|
|
1310
|
+
startGlide(prevCursorRef.current ?? cursorEntryPoint(), destination, GESTURE_GLIDE_MS, () => {
|
|
1311
|
+
if (target.mode === "act") {
|
|
1312
|
+
after(GESTURE_ACT_DELAY_MS, () => target.onAct?.());
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
return cancelAnimation;
|
|
1316
|
+
}
|
|
1317
|
+
if (!target?.element?.isConnected) {
|
|
1299
1318
|
setPhase("idle");
|
|
1300
1319
|
setRingBox(null);
|
|
1301
1320
|
setCursor(null);
|
|
@@ -1346,11 +1365,11 @@ function AgentCursor({
|
|
|
1346
1365
|
return cancelAnimation;
|
|
1347
1366
|
}, [target, animated]);
|
|
1348
1367
|
useEffect6(() => {
|
|
1349
|
-
if (!target || phase === "idle")
|
|
1368
|
+
if (!target || target.kind === "gesture" || phase === "idle")
|
|
1350
1369
|
return;
|
|
1351
1370
|
const track = () => {
|
|
1352
1371
|
trackFrameRef.current = null;
|
|
1353
|
-
if (!target.element
|
|
1372
|
+
if (!target.element?.isConnected)
|
|
1354
1373
|
return;
|
|
1355
1374
|
const rect = getRectInTopViewport(target.element);
|
|
1356
1375
|
setRingBox(rect);
|
|
@@ -1424,6 +1443,21 @@ function AgentCursor({
|
|
|
1424
1443
|
})
|
|
1425
1444
|
]
|
|
1426
1445
|
}),
|
|
1446
|
+
target.kind === "gesture" && /* @__PURE__ */ jsx("div", {
|
|
1447
|
+
role: "status",
|
|
1448
|
+
className: "skippr:fixed skippr:-translate-x-1/2 skippr:flex skippr:items-center skippr:gap-1.5 skippr:rounded-full skippr:bg-[#2bc0ae] skippr:px-2.5 skippr:py-1 skippr:shadow-[0_4px_12px_rgba(0,0,0,0.12)]",
|
|
1449
|
+
style: {
|
|
1450
|
+
left: `${cursor.x}px`,
|
|
1451
|
+
top: `${cursor.y + 18}px`,
|
|
1452
|
+
zIndex: Z_INDEX,
|
|
1453
|
+
pointerEvents: "none",
|
|
1454
|
+
transition: `left ${glideMs}ms ${GLIDE_EASE}, top ${glideMs}ms ${GLIDE_EASE}`
|
|
1455
|
+
},
|
|
1456
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1457
|
+
className: "skippr:text-[11px] skippr:font-medium skippr:text-white skippr:whitespace-nowrap",
|
|
1458
|
+
children: target.label
|
|
1459
|
+
})
|
|
1460
|
+
}),
|
|
1427
1461
|
/* @__PURE__ */ jsx("div", {
|
|
1428
1462
|
ref: cursorElRef,
|
|
1429
1463
|
style: {
|
|
@@ -1671,6 +1705,24 @@ function isInteractive(element, role) {
|
|
|
1671
1705
|
function isBlindRegion(role) {
|
|
1672
1706
|
return role === "video" || role === "iframe" || role === "canvas" || role === "audio" || role === "embed" || role === "object";
|
|
1673
1707
|
}
|
|
1708
|
+
var SCROLLABLE_CONTAINER_ROLES = new Set([
|
|
1709
|
+
"listbox",
|
|
1710
|
+
"menu",
|
|
1711
|
+
"dialog",
|
|
1712
|
+
"list",
|
|
1713
|
+
"grid",
|
|
1714
|
+
"tree",
|
|
1715
|
+
"group",
|
|
1716
|
+
"region"
|
|
1717
|
+
]);
|
|
1718
|
+
function isScrollableContainer(element, role) {
|
|
1719
|
+
if (!SCROLLABLE_CONTAINER_ROLES.has(role))
|
|
1720
|
+
return false;
|
|
1721
|
+
if (element.scrollHeight - element.clientHeight <= 1)
|
|
1722
|
+
return false;
|
|
1723
|
+
const overflowY = window.getComputedStyle(element).overflowY;
|
|
1724
|
+
return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
|
|
1725
|
+
}
|
|
1674
1726
|
function getIframeHost(element) {
|
|
1675
1727
|
const src = element.getAttribute("src");
|
|
1676
1728
|
if (!src)
|
|
@@ -1832,6 +1884,8 @@ function extractAttrs(element, role) {
|
|
|
1832
1884
|
if (/\.gif(\?|$)/i.test(imageSrc))
|
|
1833
1885
|
attributes.push("animated");
|
|
1834
1886
|
}
|
|
1887
|
+
if (isScrollableContainer(element, role))
|
|
1888
|
+
attributes.push("scrollable");
|
|
1835
1889
|
const isNativelyDisabled = "disabled" in element && element.disabled === true;
|
|
1836
1890
|
if (isNativelyDisabled || element.getAttribute("aria-disabled") === "true") {
|
|
1837
1891
|
attributes.push("disabled");
|
|
@@ -1867,7 +1921,7 @@ function shouldSkipSubtree(element) {
|
|
|
1867
1921
|
function shouldEmitElement(element, role) {
|
|
1868
1922
|
if (role === null)
|
|
1869
1923
|
return false;
|
|
1870
|
-
return isInteractive(element, role) || role === "heading" || role === "image" || role === "label" || isBlindRegion(role);
|
|
1924
|
+
return isInteractive(element, role) || role === "heading" || role === "image" || role === "label" || isBlindRegion(role) || isScrollableContainer(element, role);
|
|
1871
1925
|
}
|
|
1872
1926
|
function getSameOriginIframeBody(element) {
|
|
1873
1927
|
if (element.tagName !== "IFRAME")
|
|
@@ -2160,8 +2214,9 @@ async function snapToCanvas(element, options = {}) {
|
|
|
2160
2214
|
}
|
|
2161
2215
|
|
|
2162
2216
|
// src/components/DomCapture.tsx
|
|
2163
|
-
var SNAPSHOT_INTERVAL_MS =
|
|
2217
|
+
var SNAPSHOT_INTERVAL_MS = 3000;
|
|
2164
2218
|
var A11Y_PUBLISH_INTERVAL_MS = 2000;
|
|
2219
|
+
var FIRST_SNAPSHOT_DELAY_MS = 400;
|
|
2165
2220
|
var CAPTURE_PRESET = ScreenSharePresets.h1080fps30;
|
|
2166
2221
|
var CAPTURE_FPS = CAPTURE_PRESET.encoding.maxFramerate ?? 30;
|
|
2167
2222
|
var CAPTURE_BITRATE = 4000000;
|
|
@@ -2204,7 +2259,7 @@ async function paintViewportSnapshot(canvas, ctx) {
|
|
|
2204
2259
|
filter: shouldIncludeInSnapshot,
|
|
2205
2260
|
filterMode: "remove",
|
|
2206
2261
|
backgroundColor: "#ffffff",
|
|
2207
|
-
fast:
|
|
2262
|
+
fast: false,
|
|
2208
2263
|
dpr
|
|
2209
2264
|
});
|
|
2210
2265
|
const sourceX = window.scrollX * dpr;
|
|
@@ -2346,14 +2401,23 @@ function DomCapture() {
|
|
|
2346
2401
|
tickSnapshot();
|
|
2347
2402
|
}
|
|
2348
2403
|
});
|
|
2349
|
-
|
|
2404
|
+
let snapshotTimer = null;
|
|
2405
|
+
const scheduleNextSnapshot = (delay) => {
|
|
2406
|
+
if (cancelled)
|
|
2407
|
+
return;
|
|
2408
|
+
snapshotTimer = setTimeout(async () => {
|
|
2409
|
+
await tickSnapshot();
|
|
2410
|
+
scheduleNextSnapshot(SNAPSHOT_INTERVAL_MS);
|
|
2411
|
+
}, delay);
|
|
2412
|
+
};
|
|
2413
|
+
scheduleNextSnapshot(FIRST_SNAPSHOT_DELAY_MS);
|
|
2350
2414
|
tickA11yPublish();
|
|
2351
|
-
const snapshotTimer = setInterval(tickSnapshot, SNAPSHOT_INTERVAL_MS);
|
|
2352
2415
|
const a11yPublishTimer = setInterval(tickA11yPublish, A11Y_PUBLISH_INTERVAL_MS);
|
|
2353
2416
|
return () => {
|
|
2354
2417
|
cancelled = true;
|
|
2355
2418
|
didStartRef.current = false;
|
|
2356
|
-
|
|
2419
|
+
if (snapshotTimer)
|
|
2420
|
+
clearTimeout(snapshotTimer);
|
|
2357
2421
|
clearInterval(a11yPublishTimer);
|
|
2358
2422
|
cleanupDomEventListeners();
|
|
2359
2423
|
unpublishAndStopTrack(localParticipant, videoTrack);
|
|
@@ -2862,6 +2926,9 @@ import { useDataChannel as useDataChannel2 } from "@livekit/components-react/hoo
|
|
|
2862
2926
|
import { useCallback as useCallback6, useEffect as useEffect12, useRef as useRef8 } from "react";
|
|
2863
2927
|
var STEP_MAX_WAIT_MS = 5000;
|
|
2864
2928
|
var textDecoder2 = new TextDecoder;
|
|
2929
|
+
function parseScrollDirection(value) {
|
|
2930
|
+
return value === "down" || value === "up" || value === "top" || value === "bottom" ? value : null;
|
|
2931
|
+
}
|
|
2865
2932
|
function parsePageAction(payload) {
|
|
2866
2933
|
try {
|
|
2867
2934
|
const json = JSON.parse(textDecoder2.decode(payload));
|
|
@@ -2876,6 +2943,16 @@ function parsePageAction(payload) {
|
|
|
2876
2943
|
return typeof json.ref === "string" && typeof json.value === "string" ? { type: "select", ref: json.ref, value: json.value } : null;
|
|
2877
2944
|
case "scroll":
|
|
2878
2945
|
return typeof json.ref === "string" ? { type: "scroll", ref: json.ref } : null;
|
|
2946
|
+
case "hover":
|
|
2947
|
+
return typeof json.ref === "string" ? { type: "hover", ref: json.ref } : null;
|
|
2948
|
+
case "scroll_page": {
|
|
2949
|
+
const direction = parseScrollDirection(json.direction);
|
|
2950
|
+
return direction ? { type: "scroll_page", direction } : null;
|
|
2951
|
+
}
|
|
2952
|
+
case "scroll_in": {
|
|
2953
|
+
const direction = parseScrollDirection(json.direction);
|
|
2954
|
+
return typeof json.ref === "string" && direction ? { type: "scroll_in", ref: json.ref, direction } : null;
|
|
2955
|
+
}
|
|
2879
2956
|
case "navigate":
|
|
2880
2957
|
return typeof json.url === "string" ? { type: "navigate", url: json.url } : null;
|
|
2881
2958
|
default:
|
|
@@ -2919,6 +2996,12 @@ function performFill(element, value) {
|
|
|
2919
2996
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
2920
2997
|
element.focus();
|
|
2921
2998
|
setReactCompatibleValue(element, value);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (element.isContentEditable) {
|
|
3002
|
+
element.focus();
|
|
3003
|
+
element.textContent = value;
|
|
3004
|
+
element.dispatchEvent(new InputEvent("input", { bubbles: true }));
|
|
2922
3005
|
}
|
|
2923
3006
|
}
|
|
2924
3007
|
function performSelect(element, value) {
|
|
@@ -2965,6 +3048,87 @@ function performClick(element) {
|
|
|
2965
3048
|
} catch {}
|
|
2966
3049
|
element.click();
|
|
2967
3050
|
}
|
|
3051
|
+
function performHover(element) {
|
|
3052
|
+
const rect = element.getBoundingClientRect();
|
|
3053
|
+
const base = {
|
|
3054
|
+
bubbles: true,
|
|
3055
|
+
cancelable: true,
|
|
3056
|
+
composed: true,
|
|
3057
|
+
clientX: rect.left + rect.width / 2,
|
|
3058
|
+
clientY: rect.top + rect.height / 2
|
|
3059
|
+
};
|
|
3060
|
+
try {
|
|
3061
|
+
element.dispatchEvent(new PointerEvent("pointerover", { ...base, pointerId: 1, isPrimary: true }));
|
|
3062
|
+
} catch {}
|
|
3063
|
+
element.dispatchEvent(new MouseEvent("mouseover", base));
|
|
3064
|
+
try {
|
|
3065
|
+
element.dispatchEvent(new PointerEvent("pointerenter", { ...base, pointerId: 1, isPrimary: true }));
|
|
3066
|
+
} catch {}
|
|
3067
|
+
element.dispatchEvent(new MouseEvent("mouseenter", base));
|
|
3068
|
+
}
|
|
3069
|
+
var SCROLL_PAGE_FRACTION = 0.85;
|
|
3070
|
+
function isScrollableY(element) {
|
|
3071
|
+
if (element.scrollHeight - element.clientHeight <= 1)
|
|
3072
|
+
return false;
|
|
3073
|
+
const overflowY = element.ownerDocument.defaultView?.getComputedStyle(element).overflowY;
|
|
3074
|
+
return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
|
|
3075
|
+
}
|
|
3076
|
+
function nearestScrollableContainer(element) {
|
|
3077
|
+
let node = element;
|
|
3078
|
+
while (node && node !== document.body && node !== document.documentElement) {
|
|
3079
|
+
if (isScrollableY(node))
|
|
3080
|
+
return node;
|
|
3081
|
+
node = node.parentElement;
|
|
3082
|
+
}
|
|
3083
|
+
return document.documentElement;
|
|
3084
|
+
}
|
|
3085
|
+
var PAGE_SCROLLER_MAX_DEPTH = 3;
|
|
3086
|
+
function collectShallow(element, depth, out) {
|
|
3087
|
+
for (const child of Array.from(element.children)) {
|
|
3088
|
+
if (child instanceof HTMLElement)
|
|
3089
|
+
out.push(child);
|
|
3090
|
+
if (depth > 1)
|
|
3091
|
+
collectShallow(child, depth - 1, out);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
function pickPageScroller() {
|
|
3095
|
+
const documentScroller = document.scrollingElement;
|
|
3096
|
+
if (documentScroller && documentScroller.scrollHeight - documentScroller.clientHeight > 1) {
|
|
3097
|
+
return documentScroller;
|
|
3098
|
+
}
|
|
3099
|
+
const candidates = [];
|
|
3100
|
+
collectShallow(document.body, PAGE_SCROLLER_MAX_DEPTH, candidates);
|
|
3101
|
+
let largest = null;
|
|
3102
|
+
let largestArea = 0;
|
|
3103
|
+
for (const element of candidates) {
|
|
3104
|
+
if (!isScrollableY(element))
|
|
3105
|
+
continue;
|
|
3106
|
+
const rect = element.getBoundingClientRect();
|
|
3107
|
+
if (rect.bottom <= 0 || rect.top >= window.innerHeight)
|
|
3108
|
+
continue;
|
|
3109
|
+
const area = rect.width * rect.height;
|
|
3110
|
+
if (area > largestArea) {
|
|
3111
|
+
largestArea = area;
|
|
3112
|
+
largest = element;
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
return largest ?? documentScroller ?? document.documentElement;
|
|
3116
|
+
}
|
|
3117
|
+
function gestureFor(direction) {
|
|
3118
|
+
return direction === "up" || direction === "top" ? "up" : "down";
|
|
3119
|
+
}
|
|
3120
|
+
function performScroll(container, direction) {
|
|
3121
|
+
if (direction === "top") {
|
|
3122
|
+
container.scrollTo({ top: 0, behavior: "smooth" });
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
if (direction === "bottom") {
|
|
3126
|
+
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const delta = container.clientHeight * SCROLL_PAGE_FRACTION * (direction === "down" ? 1 : -1);
|
|
3130
|
+
container.scrollBy({ top: delta, behavior: "smooth" });
|
|
3131
|
+
}
|
|
2968
3132
|
function performAction(action, element) {
|
|
2969
3133
|
switch (action.type) {
|
|
2970
3134
|
case "click": {
|
|
@@ -2984,6 +3148,12 @@ function performAction(action, element) {
|
|
|
2984
3148
|
case "scroll":
|
|
2985
3149
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2986
3150
|
return;
|
|
3151
|
+
case "hover":
|
|
3152
|
+
performHover(element);
|
|
3153
|
+
return;
|
|
3154
|
+
case "scroll_in":
|
|
3155
|
+
performScroll(nearestScrollableContainer(element), action.direction);
|
|
3156
|
+
return;
|
|
2987
3157
|
}
|
|
2988
3158
|
}
|
|
2989
3159
|
function labelFor(action) {
|
|
@@ -2995,7 +3165,11 @@ function labelFor(action) {
|
|
|
2995
3165
|
case "select":
|
|
2996
3166
|
return "Selecting…";
|
|
2997
3167
|
case "scroll":
|
|
3168
|
+
case "scroll_page":
|
|
3169
|
+
case "scroll_in":
|
|
2998
3170
|
return "Scrolling…";
|
|
3171
|
+
case "hover":
|
|
3172
|
+
return "Hovering…";
|
|
2999
3173
|
case "navigate":
|
|
3000
3174
|
return "Navigating…";
|
|
3001
3175
|
}
|
|
@@ -3013,6 +3187,30 @@ function PageActionHandler() {
|
|
|
3013
3187
|
resolve();
|
|
3014
3188
|
return;
|
|
3015
3189
|
}
|
|
3190
|
+
if (action.type === "scroll_page") {
|
|
3191
|
+
let scrolled = false;
|
|
3192
|
+
const finishScroll = () => {
|
|
3193
|
+
if (scrolled)
|
|
3194
|
+
return;
|
|
3195
|
+
scrolled = true;
|
|
3196
|
+
resolve();
|
|
3197
|
+
};
|
|
3198
|
+
const fallback2 = setTimeout(finishScroll, STEP_MAX_WAIT_MS);
|
|
3199
|
+
setCursorTarget("act", {
|
|
3200
|
+
kind: "gesture",
|
|
3201
|
+
gesture: { direction: gestureFor(action.direction) },
|
|
3202
|
+
label: labelFor(action),
|
|
3203
|
+
mode: "act",
|
|
3204
|
+
onAct: () => {
|
|
3205
|
+
clearTimeout(fallback2);
|
|
3206
|
+
if (mountedRef.current) {
|
|
3207
|
+
runAgentAction(() => performScroll(pickPageScroller(), action.direction));
|
|
3208
|
+
}
|
|
3209
|
+
finishScroll();
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3016
3214
|
const element = findElementByRef(action.ref);
|
|
3017
3215
|
if (!element?.isConnected || !isVisible2(element) || isActionBlocked(element)) {
|
|
3018
3216
|
resolve();
|