@runtypelabs/persona 3.30.0 → 3.31.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/README.md +15 -2602
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-B_Qazlm4.d.cts → types-quh7NmYD.d.cts} +9 -0
- package/dist/animations/{types-B_Qazlm4.d.ts → types-quh7NmYD.d.ts} +9 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/index.cjs +46 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.global.js +55 -55
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +45 -45
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +50 -0
- package/dist/smart-dom-reader.d.ts +50 -0
- package/dist/theme-editor.cjs +39 -39
- package/dist/theme-editor.d.cts +50 -0
- package/dist/theme-editor.d.ts +50 -0
- package/dist/theme-editor.js +39 -39
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +19 -0
- package/package.json +1 -1
- package/src/client.ts +6 -0
- package/src/components/approval-bubble.test.ts +52 -0
- package/src/components/approval-bubble.ts +20 -0
- package/src/components/panel.ts +7 -1
- package/src/defaults.ts +14 -0
- package/src/generated/runtype-openapi-contract.ts +4 -0
- package/src/session.test.ts +62 -0
- package/src/session.ts +27 -0
- package/src/styles/widget.css +19 -0
- package/src/theme-reference.ts +1 -1
- package/src/types.ts +50 -0
- package/src/ui.scroll.test.ts +383 -0
- package/src/ui.ts +390 -40
- package/src/utils/auto-follow.test.ts +91 -0
- package/src/utils/auto-follow.ts +68 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
computeAnchorScrollState,
|
|
5
|
+
computeShrunkSpacerHeight,
|
|
4
6
|
createFollowStateController,
|
|
5
7
|
getScrollBottomOffset,
|
|
8
|
+
hasSelectionWithin,
|
|
6
9
|
isElementNearBottom,
|
|
7
10
|
resolveFollowStateFromScroll,
|
|
8
11
|
resolveFollowStateFromWheel
|
|
@@ -90,6 +93,94 @@ describe("auto-follow utilities", () => {
|
|
|
90
93
|
expect(resume.action).toBe("resume");
|
|
91
94
|
});
|
|
92
95
|
|
|
96
|
+
it("detects an active selection touching the container", () => {
|
|
97
|
+
const inside = { name: "inside" } as unknown as Node;
|
|
98
|
+
const outside = { name: "outside" } as unknown as Node;
|
|
99
|
+
const container = {
|
|
100
|
+
contains: (node: Node | null) => node === inside
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
expect(hasSelectionWithin(null, container)).toBe(false);
|
|
104
|
+
expect(
|
|
105
|
+
hasSelectionWithin(
|
|
106
|
+
{ isCollapsed: true, anchorNode: inside, focusNode: inside },
|
|
107
|
+
container
|
|
108
|
+
)
|
|
109
|
+
).toBe(false);
|
|
110
|
+
expect(
|
|
111
|
+
hasSelectionWithin(
|
|
112
|
+
{ isCollapsed: false, anchorNode: outside, focusNode: outside },
|
|
113
|
+
container
|
|
114
|
+
)
|
|
115
|
+
).toBe(false);
|
|
116
|
+
expect(
|
|
117
|
+
hasSelectionWithin(
|
|
118
|
+
{ isCollapsed: false, anchorNode: inside, focusNode: outside },
|
|
119
|
+
container
|
|
120
|
+
)
|
|
121
|
+
).toBe(true);
|
|
122
|
+
expect(
|
|
123
|
+
hasSelectionWithin(
|
|
124
|
+
{ isCollapsed: false, anchorNode: outside, focusNode: inside },
|
|
125
|
+
container
|
|
126
|
+
)
|
|
127
|
+
).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("computes anchor-top scroll geometry", () => {
|
|
131
|
+
// Anchored message deep in the transcript: spacer fills the shortfall so
|
|
132
|
+
// the target position is reachable.
|
|
133
|
+
expect(
|
|
134
|
+
computeAnchorScrollState({
|
|
135
|
+
anchorOffsetTop: 700,
|
|
136
|
+
topOffset: 16,
|
|
137
|
+
viewportHeight: 400,
|
|
138
|
+
contentHeight: 1000
|
|
139
|
+
})
|
|
140
|
+
).toEqual({ targetScrollTop: 684, spacerHeight: 84 });
|
|
141
|
+
|
|
142
|
+
// Message near the top of the transcript: target clamps to 0.
|
|
143
|
+
expect(
|
|
144
|
+
computeAnchorScrollState({
|
|
145
|
+
anchorOffsetTop: 10,
|
|
146
|
+
topOffset: 16,
|
|
147
|
+
viewportHeight: 400,
|
|
148
|
+
contentHeight: 1000
|
|
149
|
+
}).targetScrollTop
|
|
150
|
+
).toBe(0);
|
|
151
|
+
|
|
152
|
+
// Already enough content below: no spacer needed.
|
|
153
|
+
expect(
|
|
154
|
+
computeAnchorScrollState({
|
|
155
|
+
anchorOffsetTop: 500,
|
|
156
|
+
topOffset: 16,
|
|
157
|
+
viewportHeight: 400,
|
|
158
|
+
contentHeight: 2000
|
|
159
|
+
}).spacerHeight
|
|
160
|
+
).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("shrinks the anchor spacer as content grows, never below zero or above initial", () => {
|
|
164
|
+
const base = {
|
|
165
|
+
initialSpacerHeight: 100,
|
|
166
|
+
contentHeightAtAnchor: 1000
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
expect(
|
|
170
|
+
computeShrunkSpacerHeight({ ...base, currentContentHeight: 1000 })
|
|
171
|
+
).toBe(100);
|
|
172
|
+
expect(
|
|
173
|
+
computeShrunkSpacerHeight({ ...base, currentContentHeight: 1040 })
|
|
174
|
+
).toBe(60);
|
|
175
|
+
expect(
|
|
176
|
+
computeShrunkSpacerHeight({ ...base, currentContentHeight: 1500 })
|
|
177
|
+
).toBe(0);
|
|
178
|
+
// Content shrank (e.g. a collapsed tool row): spacer never grows back.
|
|
179
|
+
expect(
|
|
180
|
+
computeShrunkSpacerHeight({ ...base, currentContentHeight: 900 })
|
|
181
|
+
).toBe(100);
|
|
182
|
+
});
|
|
183
|
+
|
|
93
184
|
it("resolves wheel intent for pause and resume", () => {
|
|
94
185
|
expect(
|
|
95
186
|
resolveFollowStateFromWheel({
|
package/src/utils/auto-follow.ts
CHANGED
|
@@ -110,3 +110,71 @@ export function resolveFollowStateFromWheel(
|
|
|
110
110
|
|
|
111
111
|
return "none";
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
export type SelectionLike = Pick<Selection, "isCollapsed"> & {
|
|
115
|
+
anchorNode: Node | null;
|
|
116
|
+
focusNode: Node | null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* True when the user has a non-collapsed text selection that touches the
|
|
121
|
+
* given container. Used to pause auto-follow while the user is selecting
|
|
122
|
+
* transcript text, so the streaming scroll doesn't drag content out from
|
|
123
|
+
* under an in-progress drag-selection.
|
|
124
|
+
*/
|
|
125
|
+
export function hasSelectionWithin(
|
|
126
|
+
selection: SelectionLike | null,
|
|
127
|
+
container: Pick<Node, "contains">
|
|
128
|
+
): boolean {
|
|
129
|
+
if (!selection || selection.isCollapsed) return false;
|
|
130
|
+
return (
|
|
131
|
+
container.contains(selection.anchorNode) ||
|
|
132
|
+
container.contains(selection.focusNode)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type AnchorScrollInput = {
|
|
137
|
+
/** Top of the anchored user message, relative to the scroll content. */
|
|
138
|
+
anchorOffsetTop: number;
|
|
139
|
+
/** Desired gap kept between the anchored message and the viewport top. */
|
|
140
|
+
topOffset: number;
|
|
141
|
+
/** clientHeight of the scroll container. */
|
|
142
|
+
viewportHeight: number;
|
|
143
|
+
/** scrollHeight of the scroll container, excluding any anchor spacer. */
|
|
144
|
+
contentHeight: number;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Geometry for anchor-top ("pin the sent user message near the viewport
|
|
149
|
+
* top") scrolling. The spacer height is the extra scrollable room needed so
|
|
150
|
+
* the target scroll position is reachable before the streamed response has
|
|
151
|
+
* grown tall enough to provide it naturally.
|
|
152
|
+
*/
|
|
153
|
+
export function computeAnchorScrollState(input: AnchorScrollInput): {
|
|
154
|
+
targetScrollTop: number;
|
|
155
|
+
spacerHeight: number;
|
|
156
|
+
} {
|
|
157
|
+
const targetScrollTop = Math.max(0, input.anchorOffsetTop - input.topOffset);
|
|
158
|
+
const spacerHeight = Math.max(
|
|
159
|
+
0,
|
|
160
|
+
targetScrollTop + input.viewportHeight - input.contentHeight
|
|
161
|
+
);
|
|
162
|
+
return { targetScrollTop, spacerHeight };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Shrink-only spacer update: as streamed content grows beneath the anchored
|
|
167
|
+
* message, the spacer gives back exactly that much room so total scroll
|
|
168
|
+
* height stays constant and nothing jumps. Never grows after anchoring.
|
|
169
|
+
*/
|
|
170
|
+
export function computeShrunkSpacerHeight(input: {
|
|
171
|
+
initialSpacerHeight: number;
|
|
172
|
+
contentHeightAtAnchor: number;
|
|
173
|
+
currentContentHeight: number;
|
|
174
|
+
}): number {
|
|
175
|
+
const growth = Math.max(
|
|
176
|
+
0,
|
|
177
|
+
input.currentContentHeight - input.contentHeightAtAnchor
|
|
178
|
+
);
|
|
179
|
+
return Math.max(0, input.initialSpacerHeight - growth);
|
|
180
|
+
}
|