@sensiblestats/widget-react 0.43.0 → 0.44.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/CHANGELOG.md +17 -0
- package/dist/index.cjs +204 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +130 -13
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -279,6 +279,18 @@ function isTerminal(result) {
|
|
|
279
279
|
if (result.status === "price_changed") return result.accepted;
|
|
280
280
|
return result.status === "added" || result.status === "duplicate";
|
|
281
281
|
}
|
|
282
|
+
function slipOutcome(result) {
|
|
283
|
+
if (!result) return "added";
|
|
284
|
+
switch (result.status) {
|
|
285
|
+
case "added":
|
|
286
|
+
case "duplicate":
|
|
287
|
+
return result.status;
|
|
288
|
+
case "price_changed":
|
|
289
|
+
return result.accepted ? "added" : "price_changed";
|
|
290
|
+
case "rejected":
|
|
291
|
+
return result.reason;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
282
294
|
function resultToToast(result, shownOdds, ui) {
|
|
283
295
|
if (!result) return { kind: "success", text: ui.slipAdded };
|
|
284
296
|
switch (result.status) {
|
|
@@ -300,8 +312,28 @@ import { ENTITY_TYPE_CODES } from "@sensiblestats/widget-sdk";
|
|
|
300
312
|
function isSignalEntityType(value) {
|
|
301
313
|
return Object.prototype.hasOwnProperty.call(ENTITY_TYPE_CODES, value);
|
|
302
314
|
}
|
|
303
|
-
function
|
|
315
|
+
function toWidgetEntityType(value) {
|
|
316
|
+
switch (value.toLowerCase()) {
|
|
317
|
+
case "match":
|
|
318
|
+
return "match";
|
|
319
|
+
case "team":
|
|
320
|
+
return "team";
|
|
321
|
+
case "player":
|
|
322
|
+
return "player";
|
|
323
|
+
case "competition":
|
|
324
|
+
return "competition";
|
|
325
|
+
case "market":
|
|
326
|
+
return "market";
|
|
327
|
+
default:
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function createDashboardTapSignal(sendSignal, log) {
|
|
304
332
|
return (tile, index, layout) => {
|
|
333
|
+
const widgetEntityType = toWidgetEntityType(tile.signal.entityType);
|
|
334
|
+
if (widgetEntityType) {
|
|
335
|
+
log?.log({ name: "tile_tap", entityType: widgetEntityType, entityId: tile.signal.entityId });
|
|
336
|
+
}
|
|
305
337
|
if (!isSignalEntityType(tile.signal.entityType)) return;
|
|
306
338
|
try {
|
|
307
339
|
void sendSignal({
|
|
@@ -1373,6 +1405,13 @@ function useLiveMonitor(opts) {
|
|
|
1373
1405
|
};
|
|
1374
1406
|
}
|
|
1375
1407
|
|
|
1408
|
+
// src/eventLogContext.ts
|
|
1409
|
+
import { createContext, useContext } from "react";
|
|
1410
|
+
var EventLogContext = createContext(null);
|
|
1411
|
+
function useEventLog() {
|
|
1412
|
+
return useContext(EventLogContext);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1376
1415
|
// src/components/ChatFab.tsx
|
|
1377
1416
|
import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
|
|
1378
1417
|
|
|
@@ -1701,19 +1740,32 @@ function withTimeout(value) {
|
|
|
1701
1740
|
new Promise((_, reject) => setTimeout(() => reject(new Error("add-to-slip timed out")), ADD_TO_SLIP_TIMEOUT_MS))
|
|
1702
1741
|
]);
|
|
1703
1742
|
}
|
|
1704
|
-
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1743
|
+
function SlipButton({ selection, shownOdds, marketName, marketDeveloperName, slip, ui }) {
|
|
1705
1744
|
const [pending, setPending] = useState6(false);
|
|
1706
1745
|
const [settled, setSettled] = useState6(false);
|
|
1707
1746
|
const [toast, setToast] = useState6(null);
|
|
1708
1747
|
const dismiss = useCallback3(() => setToast(null), []);
|
|
1748
|
+
const log = useEventLog();
|
|
1709
1749
|
const handler = slip.onAddToSlip;
|
|
1710
1750
|
if (!ui || !handler || !selection) return null;
|
|
1711
1751
|
const onClick = async () => {
|
|
1712
1752
|
setPending(true);
|
|
1753
|
+
if (marketDeveloperName !== void 0) {
|
|
1754
|
+
log?.log({ name: "slip_click", entityType: "market", marketDeveloperName, entityId: marketDeveloperName });
|
|
1755
|
+
}
|
|
1713
1756
|
try {
|
|
1714
1757
|
const raw = await withTimeout(Promise.resolve(handler(selection)));
|
|
1715
1758
|
if (raw != null && !isAddToSlipResult(raw)) throw new Error("malformed add-to-slip result");
|
|
1716
1759
|
const result = raw ?? void 0;
|
|
1760
|
+
if (marketDeveloperName !== void 0) {
|
|
1761
|
+
log?.log({
|
|
1762
|
+
name: "slip_result",
|
|
1763
|
+
entityType: "market",
|
|
1764
|
+
marketDeveloperName,
|
|
1765
|
+
entityId: marketDeveloperName,
|
|
1766
|
+
outcome: slipOutcome(result)
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1717
1769
|
setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
|
|
1718
1770
|
setSettled(isTerminal(result));
|
|
1719
1771
|
if (wasAdded(result) && slip.onMarketClicked) {
|
|
@@ -1723,6 +1775,9 @@ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
|
1723
1775
|
}
|
|
1724
1776
|
}
|
|
1725
1777
|
} catch {
|
|
1778
|
+
if (marketDeveloperName !== void 0) {
|
|
1779
|
+
log?.log({ name: "slip_result", entityType: "market", marketDeveloperName, entityId: marketDeveloperName, outcome: "timeout" });
|
|
1780
|
+
}
|
|
1726
1781
|
setToast({ kind: "error", text: ui.slipFailed });
|
|
1727
1782
|
} finally {
|
|
1728
1783
|
setPending(false);
|
|
@@ -1744,7 +1799,17 @@ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
|
1744
1799
|
}
|
|
1745
1800
|
function AddToSlipButton({ it, slip, ui }) {
|
|
1746
1801
|
const selection = it.addToSlipEnabled ? toSlipSelection(it, slip.conversationId) : null;
|
|
1747
|
-
return /* @__PURE__ */ jsx9(
|
|
1802
|
+
return /* @__PURE__ */ jsx9(
|
|
1803
|
+
SlipButton,
|
|
1804
|
+
{
|
|
1805
|
+
selection,
|
|
1806
|
+
shownOdds: it.odds ?? "",
|
|
1807
|
+
marketName: it.market,
|
|
1808
|
+
marketDeveloperName: it.marketDeveloperName,
|
|
1809
|
+
slip,
|
|
1810
|
+
ui
|
|
1811
|
+
}
|
|
1812
|
+
);
|
|
1748
1813
|
}
|
|
1749
1814
|
|
|
1750
1815
|
// src/components/OddsMovementBadge.tsx
|
|
@@ -1794,13 +1859,21 @@ var PIN_GLYPHS = {
|
|
|
1794
1859
|
function PinToggle({ pin, monitor, ui }) {
|
|
1795
1860
|
const [toast, setToast] = useState8(null);
|
|
1796
1861
|
const dismiss = useCallback4(() => setToast(null), []);
|
|
1862
|
+
const log = useEventLog();
|
|
1797
1863
|
if (!monitor || !monitor.enabled) return null;
|
|
1798
1864
|
const key = pinKey(pin);
|
|
1799
1865
|
const pinned = monitor.isPinned(key);
|
|
1800
1866
|
const labels = pin.kind === "match" ? { pin: ui.matchPinLabel, unpin: ui.matchUnpinLabel } : { pin: ui.pinLabel, unpin: ui.unpinLabel };
|
|
1801
1867
|
const onClick = () => {
|
|
1802
1868
|
const result = monitor.togglePin({ ...pin, key });
|
|
1803
|
-
if (result === "
|
|
1869
|
+
if ((result === "added" || result === "removed") && (pin.kind === "match" || pin.marketDeveloperName !== void 0)) {
|
|
1870
|
+
log?.log({
|
|
1871
|
+
name: result === "added" ? "pin_add" : "pin_remove",
|
|
1872
|
+
entityType: pin.kind === "match" ? "match" : "market",
|
|
1873
|
+
entityId: pin.kind === "match" ? String(pin.matchId) : pin.marketDeveloperName,
|
|
1874
|
+
marketDeveloperName: pin.marketDeveloperName
|
|
1875
|
+
});
|
|
1876
|
+
} else if (result === "match_limit") {
|
|
1804
1877
|
setToast({ kind: "error", text: ui.matchLimitToast });
|
|
1805
1878
|
} else if (result === "market_limit") {
|
|
1806
1879
|
setToast({ kind: "error", text: ui.marketLimitToast });
|
|
@@ -1857,11 +1930,19 @@ function hasStatsContent(it) {
|
|
|
1857
1930
|
}
|
|
1858
1931
|
function ReasonStrip({ it, ui }) {
|
|
1859
1932
|
const [open, setOpen] = useState9(false);
|
|
1933
|
+
const log = useEventLog();
|
|
1860
1934
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1861
1935
|
const hasStats = hasStatsContent(it);
|
|
1862
1936
|
const hasText = !!it.text;
|
|
1863
1937
|
if (!hasText && !hasStats) return null;
|
|
1864
1938
|
const expandable = hasStats || (it.text?.length ?? 0) > 90;
|
|
1939
|
+
const onToggle = () => {
|
|
1940
|
+
setOpen((v) => {
|
|
1941
|
+
const next = !v;
|
|
1942
|
+
if (next) log?.log({ name: "insight_expand", entityType: "match", entityId: String(it.matchId) });
|
|
1943
|
+
return next;
|
|
1944
|
+
});
|
|
1945
|
+
};
|
|
1865
1946
|
return /* @__PURE__ */ jsxs9("div", { className: "ss-w-bi-reason", children: [
|
|
1866
1947
|
/* @__PURE__ */ jsx13("span", { className: "ss-w-bi-chart", "aria-hidden": "true" }),
|
|
1867
1948
|
/* @__PURE__ */ jsxs9("div", { className: "ss-w-bi-reason-body", children: [
|
|
@@ -1875,7 +1956,7 @@ function ReasonStrip({ it, ui }) {
|
|
|
1875
1956
|
className: "ss-w-bi-expand",
|
|
1876
1957
|
"aria-expanded": open,
|
|
1877
1958
|
"aria-label": open ? ui?.hideStats ?? "Hide stats" : ui?.showStats ?? "Show stats",
|
|
1878
|
-
onClick:
|
|
1959
|
+
onClick: onToggle,
|
|
1879
1960
|
children: /* @__PURE__ */ jsx13("span", { className: `ss-w-bi-arrow${open ? " ss-w-bi-arrow-open" : ""}`, "aria-hidden": "true" })
|
|
1880
1961
|
}
|
|
1881
1962
|
) : null
|
|
@@ -1981,7 +2062,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1981
2062
|
"\u2713 ",
|
|
1982
2063
|
ui.inSlipLabel
|
|
1983
2064
|
] }) : null,
|
|
1984
|
-
/* @__PURE__ */ jsx14(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, slip, ui })
|
|
2065
|
+
/* @__PURE__ */ jsx14(SlipButton, { selection, shownOdds: row.odds, marketName: vm.market, marketDeveloperName: vm.marketDeveloperName, slip, ui })
|
|
1985
2066
|
] });
|
|
1986
2067
|
}
|
|
1987
2068
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
@@ -3167,7 +3248,7 @@ function MarketAccordion({ match, monitor, ui, slip }) {
|
|
|
3167
3248
|
if (markets.length === 0) return null;
|
|
3168
3249
|
const open = monitor.openMarket[match.matchId] ?? null;
|
|
3169
3250
|
return /* @__PURE__ */ jsx46("div", { className: "ss-w-acc", children: markets.map((m) => {
|
|
3170
|
-
const isOpen = open === m.marketDeveloperName;
|
|
3251
|
+
const isOpen = open === m.marketDeveloperName && !m.suspended;
|
|
3171
3252
|
const key = `${match.matchId}:${m.marketDeveloperName}`;
|
|
3172
3253
|
const entry = monitor.marketOdds[key];
|
|
3173
3254
|
return /* @__PURE__ */ jsxs41("div", { className: isOpen ? "ss-w-acc-row ss-w-acc-row-open" : "ss-w-acc-row", children: [
|
|
@@ -3176,11 +3257,12 @@ function MarketAccordion({ match, monitor, ui, slip }) {
|
|
|
3176
3257
|
"button",
|
|
3177
3258
|
{
|
|
3178
3259
|
type: "button",
|
|
3179
|
-
className: "ss-w-acc-toggle",
|
|
3260
|
+
className: m.suspended ? "ss-w-acc-toggle ss-w-acc-toggle--susp" : "ss-w-acc-toggle",
|
|
3180
3261
|
"aria-expanded": isOpen,
|
|
3262
|
+
disabled: m.suspended,
|
|
3181
3263
|
onClick: () => monitor.toggleMarket(match.matchId, m.marketDeveloperName),
|
|
3182
3264
|
children: [
|
|
3183
|
-
/* @__PURE__ */ jsx46("span", { className: "ss-w-acc-chevron", children: isOpen ? "\u25BE" : "\u25B8" }),
|
|
3265
|
+
/* @__PURE__ */ jsx46("span", { className: "ss-w-acc-chevron", children: m.suspended ? "" : isOpen ? "\u25BE" : "\u25B8" }),
|
|
3184
3266
|
/* @__PURE__ */ jsx46("span", { className: "ss-w-acc-label", children: m.marketLabel }),
|
|
3185
3267
|
m.hasInsight && /* @__PURE__ */ jsx46("span", { className: "ss-w-acc-badge", children: "\u2605" }),
|
|
3186
3268
|
m.suspended && /* @__PURE__ */ jsx46("span", { className: "ss-w-acc-susp", children: ui.accordionSuspended })
|
|
@@ -3312,6 +3394,7 @@ function MonitorMatchCard({ match, promotedPins, monitor, ui, slip }) {
|
|
|
3312
3394
|
selection,
|
|
3313
3395
|
shownOdds: typeof row.market.price === "number" ? row.market.price.toFixed(2) : "",
|
|
3314
3396
|
marketName: row.market.marketLabel,
|
|
3397
|
+
marketDeveloperName: row.market.marketDeveloperName,
|
|
3315
3398
|
slip,
|
|
3316
3399
|
ui
|
|
3317
3400
|
}
|
|
@@ -4287,6 +4370,9 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
4287
4370
|
/* Row header: expand toggle (flex:1) + the market PinToggle as a sibling (no nested buttons). */
|
|
4288
4371
|
.ss-w-acc-head { display: flex; align-items: center; gap: 8px; padding: 6px 9px 6px 11px; }
|
|
4289
4372
|
.ss-w-acc-toggle { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; background: none; border: none; cursor: pointer; text-align: left; font: inherit; color: var(--ss-w-ink); padding: 5px 0; }
|
|
4373
|
+
/* Suspended: the row stays listed (knowing a market is off is useful) but is not tappable --
|
|
4374
|
+
its card is unservable, so a pointer cursor would promise something that cannot load. */
|
|
4375
|
+
.ss-w-acc-toggle--susp { cursor: default; color: var(--ss-w-faint); }
|
|
4290
4376
|
.ss-w-acc-chevron { color: var(--ss-w-faint); font-size: 11px; flex: none; }
|
|
4291
4377
|
.ss-w-acc-label { font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4292
4378
|
.ss-w-acc-badge { font-size: 10.5px; font-weight: 700; color: var(--ss-w-accent); flex: none; }
|
|
@@ -4323,6 +4409,13 @@ function StatsWidget(props) {
|
|
|
4323
4409
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
4324
4410
|
const cfg = props.config;
|
|
4325
4411
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
4412
|
+
const logger = useMemo2(
|
|
4413
|
+
() => built ? built.client.createEventLogger({
|
|
4414
|
+
operatorUserId: cfg?.userContext?.operatorUserId,
|
|
4415
|
+
anonymousUserId: cfg?.userContext?.anonymousUserId
|
|
4416
|
+
}) : null,
|
|
4417
|
+
[built?.client]
|
|
4418
|
+
);
|
|
4326
4419
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
4327
4420
|
const rootRef = useRef8(null);
|
|
4328
4421
|
const introDoneRef = useRef8(false);
|
|
@@ -4361,9 +4454,33 @@ function StatsWidget(props) {
|
|
|
4361
4454
|
[props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
|
|
4362
4455
|
);
|
|
4363
4456
|
const onDashboardTap = useMemo2(
|
|
4364
|
-
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
4365
|
-
[built]
|
|
4457
|
+
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input), logger) : void 0,
|
|
4458
|
+
[built, logger]
|
|
4366
4459
|
);
|
|
4460
|
+
const operatorUserId = cfg?.userContext?.operatorUserId;
|
|
4461
|
+
const anonymousUserId = cfg?.userContext?.anonymousUserId;
|
|
4462
|
+
const conversationId = controller.state.conversationId ?? void 0;
|
|
4463
|
+
useEffect11(() => {
|
|
4464
|
+
if (!logger) return;
|
|
4465
|
+
logger.setIdentity({ operatorUserId, anonymousUserId, conversationId });
|
|
4466
|
+
}, [logger, operatorUserId, anonymousUserId, conversationId]);
|
|
4467
|
+
useEffect11(() => {
|
|
4468
|
+
if (!logger) return;
|
|
4469
|
+
const onHide = () => {
|
|
4470
|
+
logger.flushOnHide();
|
|
4471
|
+
};
|
|
4472
|
+
const onVisibilityHide = () => {
|
|
4473
|
+
if (document.visibilityState === "hidden") logger.flushOnHide();
|
|
4474
|
+
};
|
|
4475
|
+
window.addEventListener("pagehide", onHide);
|
|
4476
|
+
document.addEventListener("visibilitychange", onVisibilityHide);
|
|
4477
|
+
return () => {
|
|
4478
|
+
window.removeEventListener("pagehide", onHide);
|
|
4479
|
+
document.removeEventListener("visibilitychange", onVisibilityHide);
|
|
4480
|
+
logger.flushOnHide();
|
|
4481
|
+
logger.dispose();
|
|
4482
|
+
};
|
|
4483
|
+
}, [logger]);
|
|
4367
4484
|
useEffect11(() => {
|
|
4368
4485
|
if (!built || !ui.injectStyles) return;
|
|
4369
4486
|
const node = rootRef.current?.getRootNode();
|
|
@@ -4371,7 +4488,7 @@ function StatsWidget(props) {
|
|
|
4371
4488
|
}, [built, ui.injectStyles]);
|
|
4372
4489
|
if (!built) return null;
|
|
4373
4490
|
const attrs = themeAttrs(ui);
|
|
4374
|
-
return /* @__PURE__ */ jsx50("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx50(ChatPanel, { controller, ui, slip, onDashboardTap, monitor: monitor.enabled ? monitor : void 0 }) : /* @__PURE__ */ jsx50(
|
|
4491
|
+
return /* @__PURE__ */ jsx50(EventLogContext.Provider, { value: logger, children: /* @__PURE__ */ jsx50("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx50(ChatPanel, { controller, ui, slip, onDashboardTap, monitor: monitor.enabled ? monitor : void 0 }) : /* @__PURE__ */ jsx50(
|
|
4375
4492
|
ChatFab,
|
|
4376
4493
|
{
|
|
4377
4494
|
label: ui.launcherLabel,
|
|
@@ -4384,7 +4501,7 @@ function StatsWidget(props) {
|
|
|
4384
4501
|
introAllowed: !introDoneRef.current,
|
|
4385
4502
|
onIntroStart: markIntroDone
|
|
4386
4503
|
}
|
|
4387
|
-
) });
|
|
4504
|
+
) }) });
|
|
4388
4505
|
}
|
|
4389
4506
|
function emptyConversation() {
|
|
4390
4507
|
return { send: () => {
|