@sensiblestats/widget-react 0.26.0 → 0.26.2
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 +12 -0
- package/dist/index.cjs +26 -5
- 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 +26 -5
- package/dist/index.js.map +1 -1
- package/dist/styles.css +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.26.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix the new-turn scroll anchor jumping to the bottom on a follow-up send after tall content (e.g. tapping a chip below a club dashboard). Reserve a viewport of space below the new turn while it streams so its start can actually reach the top of the panel, then collapse the space once the turn completes. The turn's start now stays pinned to the top through the whole reply instead of the view following the stream to the bottom.
|
|
8
|
+
|
|
9
|
+
## 0.26.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Keep the start of a new turn in view instead of scrolling to the bottom of the reply. When a response arrives the message list now pins the start of the turn to the top — the user's question for a normal turn, or the assistant message itself for an auto-greeting or greeting dashboard — so a long answer reads from its beginning rather than landing the reader on its last line.
|
|
14
|
+
|
|
3
15
|
## 0.26.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -1843,12 +1843,12 @@ function FanDashboardCard({ card, onTap, ui }) {
|
|
|
1843
1843
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
1844
1844
|
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1845
1845
|
if (message.role === "user") {
|
|
1846
|
-
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "ss-w-msg ss-w-user", "data-msg-id": message.id, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1847
1847
|
}
|
|
1848
1848
|
const onFanDashboardTap = (card) => {
|
|
1849
1849
|
onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
|
|
1850
1850
|
};
|
|
1851
|
-
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
1851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "ss-w-msg ss-w-bot", "data-msg-id": message.id, children: [
|
|
1852
1852
|
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Markdown, { text: message.text }) : null,
|
|
1853
1853
|
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
1854
1854
|
message.clubFanDashboard ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
|
|
@@ -1875,14 +1875,31 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1875
1875
|
|
|
1876
1876
|
// src/components/MessageList.tsx
|
|
1877
1877
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
1878
|
+
function turnAnchor(messages) {
|
|
1879
|
+
const last = messages[messages.length - 1];
|
|
1880
|
+
if (!last || last.role !== "assistant") return null;
|
|
1881
|
+
const prev = messages[messages.length - 2];
|
|
1882
|
+
return { turnId: last.id, anchorId: prev && prev.role === "user" ? prev.id : last.id };
|
|
1883
|
+
}
|
|
1878
1884
|
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1879
1885
|
const { state, send } = controller;
|
|
1880
1886
|
const listRef = (0, import_react10.useRef)(null);
|
|
1881
1887
|
const endRef = (0, import_react10.useRef)(null);
|
|
1882
1888
|
const pinnedRef = (0, import_react10.useRef)(true);
|
|
1889
|
+
const anchoredTurnRef = (0, import_react10.useRef)(null);
|
|
1883
1890
|
(0, import_react10.useEffect)(() => {
|
|
1891
|
+
const anchor = turnAnchor(state.messages);
|
|
1892
|
+
const list = listRef.current;
|
|
1893
|
+
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
1894
|
+
anchoredTurnRef.current = anchor.turnId;
|
|
1895
|
+
if (endRef.current && list) endRef.current.style.height = `${list.clientHeight}px`;
|
|
1896
|
+
pinnedRef.current = false;
|
|
1897
|
+
list?.querySelector(`[data-msg-id="${anchor.anchorId}"]`)?.scrollIntoView({ block: "start" });
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
if (!state.isStreaming && endRef.current) endRef.current.style.height = "";
|
|
1884
1901
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
1885
|
-
}, [state.messages, state.status]);
|
|
1902
|
+
}, [state.messages, state.status, state.isStreaming]);
|
|
1886
1903
|
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1887
1904
|
const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
|
|
1888
1905
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
@@ -1918,7 +1935,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1918
1935
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1919
1936
|
] }) : null,
|
|
1920
1937
|
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
1921
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { ref: endRef })
|
|
1938
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { ref: endRef, className: "ss-w-turn-spacer", "aria-hidden": "true" })
|
|
1922
1939
|
]
|
|
1923
1940
|
}
|
|
1924
1941
|
);
|
|
@@ -2039,7 +2056,11 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2039
2056
|
.ss-w-close { margin-left: auto; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
2040
2057
|
|
|
2041
2058
|
/* messages */
|
|
2042
|
-
.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
|
|
2059
|
+
.ss-w-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; scroll-padding-top: 14px; }
|
|
2060
|
+
/* Transient bottom spacer: reserves a viewport of room below a new turn so its
|
|
2061
|
+
top can be scrolled into view while the reply is still streaming in. Height is
|
|
2062
|
+
set imperatively and flex must not shrink it away. */
|
|
2063
|
+
.ss-w-turn-spacer { flex: none; }
|
|
2043
2064
|
.ss-w-msg { display: flex; }
|
|
2044
2065
|
.ss-w-user { justify-content: flex-end; }
|
|
2045
2066
|
.ss-w-user .ss-w-bubble { background: color-mix(in srgb, var(--ss-w-accent) 14%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 30%, var(--ss-w-line)); border-radius: 14px; padding: 8px 12px; max-width: 80%; }
|