@skippr/live-agent-sdk 0.56.0 → 0.58.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 +241 -7
- package/dist/skippr-sdk.js +147 -147
- 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)
|
|
@@ -1748,7 +1800,47 @@ function isActionBlocked(element) {
|
|
|
1748
1800
|
function escapeAttributeValue(value) {
|
|
1749
1801
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\r\n]+/g, " ");
|
|
1750
1802
|
}
|
|
1751
|
-
|
|
1803
|
+
var GENERIC_ICON_NAMES = new Set(["icon", "icons", "image", "img", "sprite", "sprites", "asset"]);
|
|
1804
|
+
function sanitizeIconName(src) {
|
|
1805
|
+
if (!src || src.startsWith("data:"))
|
|
1806
|
+
return null;
|
|
1807
|
+
const withoutQuery = src.split(/[?#]/)[0] ?? "";
|
|
1808
|
+
const basename = withoutQuery.slice(withoutQuery.lastIndexOf("/") + 1);
|
|
1809
|
+
const core = basename.split(".")[0] ?? "";
|
|
1810
|
+
const cleaned = core.replace(/^(?:icon[-_]?|ic[-_]|svg[-_])/i, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase().replace(/\s*\bicons?$/, "").trim();
|
|
1811
|
+
if (!cleaned)
|
|
1812
|
+
return null;
|
|
1813
|
+
if (/^[0-9a-f]{8,}$/i.test(cleaned))
|
|
1814
|
+
return null;
|
|
1815
|
+
if (/^\d+$/.test(cleaned))
|
|
1816
|
+
return null;
|
|
1817
|
+
if (GENERIC_ICON_NAMES.has(cleaned))
|
|
1818
|
+
return null;
|
|
1819
|
+
if (cleaned.split(" ").length > 3)
|
|
1820
|
+
return null;
|
|
1821
|
+
return cleaned.slice(0, NAME_MAX_CHARS);
|
|
1822
|
+
}
|
|
1823
|
+
function hasInteractiveAncestorWithin(image, boundary) {
|
|
1824
|
+
let node = image.parentElement;
|
|
1825
|
+
while (node && node !== boundary) {
|
|
1826
|
+
if (isInteractive(node, inferRole(node)))
|
|
1827
|
+
return true;
|
|
1828
|
+
node = node.parentElement;
|
|
1829
|
+
}
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
function iconNameHint(element) {
|
|
1833
|
+
if (element.tagName === "IMG") {
|
|
1834
|
+
return sanitizeIconName(element.getAttribute("src") || "");
|
|
1835
|
+
}
|
|
1836
|
+
const image = element.querySelector("img[src]");
|
|
1837
|
+
if (!image)
|
|
1838
|
+
return null;
|
|
1839
|
+
if (hasInteractiveAncestorWithin(image, element))
|
|
1840
|
+
return null;
|
|
1841
|
+
return sanitizeIconName(image.getAttribute("src") || "");
|
|
1842
|
+
}
|
|
1843
|
+
function extractAttrs(element, role, name) {
|
|
1752
1844
|
const attributes = [];
|
|
1753
1845
|
if (role === "link") {
|
|
1754
1846
|
const href = element.getAttribute("href");
|
|
@@ -1832,6 +1924,13 @@ function extractAttrs(element, role) {
|
|
|
1832
1924
|
if (/\.gif(\?|$)/i.test(imageSrc))
|
|
1833
1925
|
attributes.push("animated");
|
|
1834
1926
|
}
|
|
1927
|
+
if (!name && isInteractive(element, role)) {
|
|
1928
|
+
const hint = iconNameHint(element);
|
|
1929
|
+
if (hint)
|
|
1930
|
+
attributes.push(`iconName="${escapeAttributeValue(hint)}"`);
|
|
1931
|
+
}
|
|
1932
|
+
if (isScrollableContainer(element, role))
|
|
1933
|
+
attributes.push("scrollable");
|
|
1835
1934
|
const isNativelyDisabled = "disabled" in element && element.disabled === true;
|
|
1836
1935
|
if (isNativelyDisabled || element.getAttribute("aria-disabled") === "true") {
|
|
1837
1936
|
attributes.push("disabled");
|
|
@@ -1867,7 +1966,7 @@ function shouldSkipSubtree(element) {
|
|
|
1867
1966
|
function shouldEmitElement(element, role) {
|
|
1868
1967
|
if (role === null)
|
|
1869
1968
|
return false;
|
|
1870
|
-
return isInteractive(element, role) || role === "heading" || role === "image" || role === "label" || isBlindRegion(role);
|
|
1969
|
+
return isInteractive(element, role) || role === "heading" || role === "image" || role === "label" || isBlindRegion(role) || isScrollableContainer(element, role);
|
|
1871
1970
|
}
|
|
1872
1971
|
function getSameOriginIframeBody(element) {
|
|
1873
1972
|
if (element.tagName !== "IFRAME")
|
|
@@ -1889,12 +1988,13 @@ function walkAndCollect(element, depth, entries) {
|
|
|
1889
1988
|
let childDepth = depth;
|
|
1890
1989
|
if (isVisibleAndEmittable && role) {
|
|
1891
1990
|
const ref = ensureStableRef(element);
|
|
1991
|
+
const name = accessibleName(element);
|
|
1892
1992
|
entries.push({
|
|
1893
1993
|
depth,
|
|
1894
1994
|
role,
|
|
1895
|
-
name
|
|
1995
|
+
name,
|
|
1896
1996
|
ref,
|
|
1897
|
-
attrs: extractAttrs(element, role),
|
|
1997
|
+
attrs: extractAttrs(element, role, name),
|
|
1898
1998
|
area: rect.width * rect.height
|
|
1899
1999
|
});
|
|
1900
2000
|
childDepth = depth + 1;
|
|
@@ -2872,6 +2972,9 @@ import { useDataChannel as useDataChannel2 } from "@livekit/components-react/hoo
|
|
|
2872
2972
|
import { useCallback as useCallback6, useEffect as useEffect12, useRef as useRef8 } from "react";
|
|
2873
2973
|
var STEP_MAX_WAIT_MS = 5000;
|
|
2874
2974
|
var textDecoder2 = new TextDecoder;
|
|
2975
|
+
function parseScrollDirection(value) {
|
|
2976
|
+
return value === "down" || value === "up" || value === "top" || value === "bottom" ? value : null;
|
|
2977
|
+
}
|
|
2875
2978
|
function parsePageAction(payload) {
|
|
2876
2979
|
try {
|
|
2877
2980
|
const json = JSON.parse(textDecoder2.decode(payload));
|
|
@@ -2886,6 +2989,16 @@ function parsePageAction(payload) {
|
|
|
2886
2989
|
return typeof json.ref === "string" && typeof json.value === "string" ? { type: "select", ref: json.ref, value: json.value } : null;
|
|
2887
2990
|
case "scroll":
|
|
2888
2991
|
return typeof json.ref === "string" ? { type: "scroll", ref: json.ref } : null;
|
|
2992
|
+
case "hover":
|
|
2993
|
+
return typeof json.ref === "string" ? { type: "hover", ref: json.ref } : null;
|
|
2994
|
+
case "scroll_page": {
|
|
2995
|
+
const direction = parseScrollDirection(json.direction);
|
|
2996
|
+
return direction ? { type: "scroll_page", direction } : null;
|
|
2997
|
+
}
|
|
2998
|
+
case "scroll_in": {
|
|
2999
|
+
const direction = parseScrollDirection(json.direction);
|
|
3000
|
+
return typeof json.ref === "string" && direction ? { type: "scroll_in", ref: json.ref, direction } : null;
|
|
3001
|
+
}
|
|
2889
3002
|
case "navigate":
|
|
2890
3003
|
return typeof json.url === "string" ? { type: "navigate", url: json.url } : null;
|
|
2891
3004
|
default:
|
|
@@ -2929,6 +3042,12 @@ function performFill(element, value) {
|
|
|
2929
3042
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
2930
3043
|
element.focus();
|
|
2931
3044
|
setReactCompatibleValue(element, value);
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
if (element.isContentEditable) {
|
|
3048
|
+
element.focus();
|
|
3049
|
+
element.textContent = value;
|
|
3050
|
+
element.dispatchEvent(new InputEvent("input", { bubbles: true }));
|
|
2932
3051
|
}
|
|
2933
3052
|
}
|
|
2934
3053
|
function performSelect(element, value) {
|
|
@@ -2975,6 +3094,87 @@ function performClick(element) {
|
|
|
2975
3094
|
} catch {}
|
|
2976
3095
|
element.click();
|
|
2977
3096
|
}
|
|
3097
|
+
function performHover(element) {
|
|
3098
|
+
const rect = element.getBoundingClientRect();
|
|
3099
|
+
const base = {
|
|
3100
|
+
bubbles: true,
|
|
3101
|
+
cancelable: true,
|
|
3102
|
+
composed: true,
|
|
3103
|
+
clientX: rect.left + rect.width / 2,
|
|
3104
|
+
clientY: rect.top + rect.height / 2
|
|
3105
|
+
};
|
|
3106
|
+
try {
|
|
3107
|
+
element.dispatchEvent(new PointerEvent("pointerover", { ...base, pointerId: 1, isPrimary: true }));
|
|
3108
|
+
} catch {}
|
|
3109
|
+
element.dispatchEvent(new MouseEvent("mouseover", base));
|
|
3110
|
+
try {
|
|
3111
|
+
element.dispatchEvent(new PointerEvent("pointerenter", { ...base, pointerId: 1, isPrimary: true }));
|
|
3112
|
+
} catch {}
|
|
3113
|
+
element.dispatchEvent(new MouseEvent("mouseenter", base));
|
|
3114
|
+
}
|
|
3115
|
+
var SCROLL_PAGE_FRACTION = 0.85;
|
|
3116
|
+
function isScrollableY(element) {
|
|
3117
|
+
if (element.scrollHeight - element.clientHeight <= 1)
|
|
3118
|
+
return false;
|
|
3119
|
+
const overflowY = element.ownerDocument.defaultView?.getComputedStyle(element).overflowY;
|
|
3120
|
+
return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
|
|
3121
|
+
}
|
|
3122
|
+
function nearestScrollableContainer(element) {
|
|
3123
|
+
let node = element;
|
|
3124
|
+
while (node && node !== document.body && node !== document.documentElement) {
|
|
3125
|
+
if (isScrollableY(node))
|
|
3126
|
+
return node;
|
|
3127
|
+
node = node.parentElement;
|
|
3128
|
+
}
|
|
3129
|
+
return document.documentElement;
|
|
3130
|
+
}
|
|
3131
|
+
var PAGE_SCROLLER_MAX_DEPTH = 3;
|
|
3132
|
+
function collectShallow(element, depth, out) {
|
|
3133
|
+
for (const child of Array.from(element.children)) {
|
|
3134
|
+
if (child instanceof HTMLElement)
|
|
3135
|
+
out.push(child);
|
|
3136
|
+
if (depth > 1)
|
|
3137
|
+
collectShallow(child, depth - 1, out);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
function pickPageScroller() {
|
|
3141
|
+
const documentScroller = document.scrollingElement;
|
|
3142
|
+
if (documentScroller && documentScroller.scrollHeight - documentScroller.clientHeight > 1) {
|
|
3143
|
+
return documentScroller;
|
|
3144
|
+
}
|
|
3145
|
+
const candidates = [];
|
|
3146
|
+
collectShallow(document.body, PAGE_SCROLLER_MAX_DEPTH, candidates);
|
|
3147
|
+
let largest = null;
|
|
3148
|
+
let largestArea = 0;
|
|
3149
|
+
for (const element of candidates) {
|
|
3150
|
+
if (!isScrollableY(element))
|
|
3151
|
+
continue;
|
|
3152
|
+
const rect = element.getBoundingClientRect();
|
|
3153
|
+
if (rect.bottom <= 0 || rect.top >= window.innerHeight)
|
|
3154
|
+
continue;
|
|
3155
|
+
const area = rect.width * rect.height;
|
|
3156
|
+
if (area > largestArea) {
|
|
3157
|
+
largestArea = area;
|
|
3158
|
+
largest = element;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return largest ?? documentScroller ?? document.documentElement;
|
|
3162
|
+
}
|
|
3163
|
+
function gestureFor(direction) {
|
|
3164
|
+
return direction === "up" || direction === "top" ? "up" : "down";
|
|
3165
|
+
}
|
|
3166
|
+
function performScroll(container, direction) {
|
|
3167
|
+
if (direction === "top") {
|
|
3168
|
+
container.scrollTo({ top: 0, behavior: "smooth" });
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (direction === "bottom") {
|
|
3172
|
+
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
const delta = container.clientHeight * SCROLL_PAGE_FRACTION * (direction === "down" ? 1 : -1);
|
|
3176
|
+
container.scrollBy({ top: delta, behavior: "smooth" });
|
|
3177
|
+
}
|
|
2978
3178
|
function performAction(action, element) {
|
|
2979
3179
|
switch (action.type) {
|
|
2980
3180
|
case "click": {
|
|
@@ -2994,6 +3194,12 @@ function performAction(action, element) {
|
|
|
2994
3194
|
case "scroll":
|
|
2995
3195
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2996
3196
|
return;
|
|
3197
|
+
case "hover":
|
|
3198
|
+
performHover(element);
|
|
3199
|
+
return;
|
|
3200
|
+
case "scroll_in":
|
|
3201
|
+
performScroll(nearestScrollableContainer(element), action.direction);
|
|
3202
|
+
return;
|
|
2997
3203
|
}
|
|
2998
3204
|
}
|
|
2999
3205
|
function labelFor(action) {
|
|
@@ -3005,7 +3211,11 @@ function labelFor(action) {
|
|
|
3005
3211
|
case "select":
|
|
3006
3212
|
return "Selecting…";
|
|
3007
3213
|
case "scroll":
|
|
3214
|
+
case "scroll_page":
|
|
3215
|
+
case "scroll_in":
|
|
3008
3216
|
return "Scrolling…";
|
|
3217
|
+
case "hover":
|
|
3218
|
+
return "Hovering…";
|
|
3009
3219
|
case "navigate":
|
|
3010
3220
|
return "Navigating…";
|
|
3011
3221
|
}
|
|
@@ -3023,6 +3233,30 @@ function PageActionHandler() {
|
|
|
3023
3233
|
resolve();
|
|
3024
3234
|
return;
|
|
3025
3235
|
}
|
|
3236
|
+
if (action.type === "scroll_page") {
|
|
3237
|
+
let scrolled = false;
|
|
3238
|
+
const finishScroll = () => {
|
|
3239
|
+
if (scrolled)
|
|
3240
|
+
return;
|
|
3241
|
+
scrolled = true;
|
|
3242
|
+
resolve();
|
|
3243
|
+
};
|
|
3244
|
+
const fallback2 = setTimeout(finishScroll, STEP_MAX_WAIT_MS);
|
|
3245
|
+
setCursorTarget("act", {
|
|
3246
|
+
kind: "gesture",
|
|
3247
|
+
gesture: { direction: gestureFor(action.direction) },
|
|
3248
|
+
label: labelFor(action),
|
|
3249
|
+
mode: "act",
|
|
3250
|
+
onAct: () => {
|
|
3251
|
+
clearTimeout(fallback2);
|
|
3252
|
+
if (mountedRef.current) {
|
|
3253
|
+
runAgentAction(() => performScroll(pickPageScroller(), action.direction));
|
|
3254
|
+
}
|
|
3255
|
+
finishScroll();
|
|
3256
|
+
}
|
|
3257
|
+
});
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3026
3260
|
const element = findElementByRef(action.ref);
|
|
3027
3261
|
if (!element?.isConnected || !isVisible2(element) || isActionBlocked(element)) {
|
|
3028
3262
|
resolve();
|