@runtypelabs/persona 3.6.0 → 3.8.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/index.cjs +40 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -4
- package/dist/index.d.ts +73 -4
- package/dist/index.global.js +69 -69
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +40 -40
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +704 -243
- package/dist/theme-editor.d.cts +75 -5
- package/dist/theme-editor.d.ts +75 -5
- package/dist/theme-editor.js +703 -243
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +53 -0
- package/dist/theme-reference.d.ts +53 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +44 -0
- package/package.json +1 -1
- package/src/components/artifact-card.ts +1 -1
- package/src/components/demo-carousel.ts +1 -1
- package/src/components/event-stream-view.test.ts +142 -0
- package/src/components/event-stream-view.ts +67 -28
- package/src/components/header-builder.ts +3 -0
- package/src/components/launcher.ts +7 -2
- package/src/components/panel.ts +3 -1
- package/src/defaults.ts +15 -0
- package/src/runtime/host-layout.test.ts +1 -1
- package/src/runtime/host-layout.ts +2 -1
- package/src/scroll-to-bottom-defaults.test.ts +13 -0
- package/src/styles/widget.css +44 -0
- package/src/theme-editor/index.ts +1 -0
- package/src/theme-editor/role-mappings.ts +12 -0
- package/src/theme-editor/sections.test.ts +43 -0
- package/src/theme-editor/sections.ts +42 -0
- package/src/theme-reference.ts +8 -0
- package/src/types/theme.ts +45 -0
- package/src/types.ts +31 -4
- package/src/ui.overlay-z-index.test.ts +34 -2
- package/src/ui.scroll.test.ts +554 -0
- package/src/ui.ts +264 -90
- package/src/utils/auto-follow.test.ts +110 -0
- package/src/utils/auto-follow.ts +112 -0
- package/src/utils/constants.ts +13 -0
- package/src/utils/dropdown.ts +2 -1
- package/src/utils/overlay-host-stacking.test.ts +61 -0
- package/src/utils/overlay-host-stacking.ts +38 -0
- package/src/utils/scroll-lock.test.ts +64 -0
- package/src/utils/scroll-lock.ts +62 -0
- package/src/utils/theme.test.ts +34 -0
- package/src/utils/tokens.ts +112 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type FollowStateAction = "none" | "pause" | "resume";
|
|
2
|
+
|
|
3
|
+
export type FollowStateController = {
|
|
4
|
+
isFollowing: () => boolean;
|
|
5
|
+
pause: () => boolean;
|
|
6
|
+
resume: () => boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type FollowStateScrollInput = {
|
|
10
|
+
following: boolean;
|
|
11
|
+
currentScrollTop: number;
|
|
12
|
+
lastScrollTop: number;
|
|
13
|
+
nearBottom: boolean;
|
|
14
|
+
userScrollThreshold: number;
|
|
15
|
+
isAutoScrolling?: boolean;
|
|
16
|
+
pauseOnUpwardScroll?: boolean;
|
|
17
|
+
pauseWhenAwayFromBottom?: boolean;
|
|
18
|
+
resumeRequiresDownwardScroll?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type FollowStateWheelInput = {
|
|
22
|
+
following: boolean;
|
|
23
|
+
deltaY: number;
|
|
24
|
+
nearBottom?: boolean;
|
|
25
|
+
resumeWhenNearBottom?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function createFollowStateController(initiallyFollowing = true): FollowStateController {
|
|
29
|
+
let following = initiallyFollowing;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
isFollowing: () => following,
|
|
33
|
+
pause: () => {
|
|
34
|
+
if (!following) return false;
|
|
35
|
+
following = false;
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
38
|
+
resume: () => {
|
|
39
|
+
if (following) return false;
|
|
40
|
+
following = true;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getScrollBottomOffset(element: Pick<HTMLElement, "scrollHeight" | "clientHeight">): number {
|
|
47
|
+
return Math.max(0, element.scrollHeight - element.clientHeight);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isElementNearBottom(
|
|
51
|
+
element: Pick<HTMLElement, "scrollTop" | "scrollHeight" | "clientHeight">,
|
|
52
|
+
threshold: number
|
|
53
|
+
): boolean {
|
|
54
|
+
return getScrollBottomOffset(element) - element.scrollTop <= threshold;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveFollowStateFromScroll(
|
|
58
|
+
input: FollowStateScrollInput
|
|
59
|
+
): { action: FollowStateAction; delta: number; nextLastScrollTop: number } {
|
|
60
|
+
const {
|
|
61
|
+
following,
|
|
62
|
+
currentScrollTop,
|
|
63
|
+
lastScrollTop,
|
|
64
|
+
nearBottom,
|
|
65
|
+
userScrollThreshold,
|
|
66
|
+
isAutoScrolling = false,
|
|
67
|
+
pauseOnUpwardScroll = false,
|
|
68
|
+
pauseWhenAwayFromBottom = true,
|
|
69
|
+
resumeRequiresDownwardScroll = false
|
|
70
|
+
} = input;
|
|
71
|
+
|
|
72
|
+
const delta = currentScrollTop - lastScrollTop;
|
|
73
|
+
|
|
74
|
+
if (isAutoScrolling || Math.abs(delta) < userScrollThreshold) {
|
|
75
|
+
return { action: "none", delta, nextLastScrollTop: currentScrollTop };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!following && nearBottom && (!resumeRequiresDownwardScroll || delta > 0)) {
|
|
79
|
+
return { action: "resume", delta, nextLastScrollTop: currentScrollTop };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (following && pauseOnUpwardScroll && delta < 0) {
|
|
83
|
+
return { action: "pause", delta, nextLastScrollTop: currentScrollTop };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (following && pauseWhenAwayFromBottom && !nearBottom) {
|
|
87
|
+
return { action: "pause", delta, nextLastScrollTop: currentScrollTop };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { action: "none", delta, nextLastScrollTop: currentScrollTop };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveFollowStateFromWheel(
|
|
94
|
+
input: FollowStateWheelInput
|
|
95
|
+
): FollowStateAction {
|
|
96
|
+
const {
|
|
97
|
+
following,
|
|
98
|
+
deltaY,
|
|
99
|
+
nearBottom = false,
|
|
100
|
+
resumeWhenNearBottom = false
|
|
101
|
+
} = input;
|
|
102
|
+
|
|
103
|
+
if (following && deltaY < 0) {
|
|
104
|
+
return "pause";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!following && resumeWhenNearBottom && deltaY > 0 && nearBottom) {
|
|
108
|
+
return "resume";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return "none";
|
|
112
|
+
}
|
package/src/utils/constants.ts
CHANGED
|
@@ -7,6 +7,19 @@ export const statusCopy: Record<AgentWidgetSessionStatus, string> = {
|
|
|
7
7
|
error: "Offline"
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Default z-index for widget overlays. Used for the floating panel, launcher
|
|
12
|
+
* button, sidebar, mobile fullscreen, and docked mobile fullscreen modes.
|
|
13
|
+
* Integrators can override via `launcher.zIndex`.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_OVERLAY_Z_INDEX = 100000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Z-index for elements portaled to document.body (tooltips, dropdowns).
|
|
19
|
+
* Must be above the widget overlay so portaled UI is not clipped.
|
|
20
|
+
*/
|
|
21
|
+
export const PORTALED_OVERLAY_Z_INDEX = DEFAULT_OVERLAY_Z_INDEX + 1;
|
|
22
|
+
|
|
10
23
|
|
|
11
24
|
|
|
12
25
|
|
package/src/utils/dropdown.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createElement } from "./dom";
|
|
2
2
|
import { renderLucideIcon } from "./icons";
|
|
3
|
+
import { PORTALED_OVERLAY_Z_INDEX } from "./constants";
|
|
3
4
|
|
|
4
5
|
export interface DropdownMenuItem {
|
|
5
6
|
id: string;
|
|
@@ -73,7 +74,7 @@ export function createDropdownMenu(options: CreateDropdownOptions): DropdownMenu
|
|
|
73
74
|
if (portal) {
|
|
74
75
|
// Fixed positioning — menu is portaled outside the anchor's overflow context
|
|
75
76
|
menu.style.position = "fixed";
|
|
76
|
-
menu.style.zIndex =
|
|
77
|
+
menu.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);
|
|
77
78
|
} else {
|
|
78
79
|
// Absolute positioning — menu lives inside the anchor
|
|
79
80
|
menu.style.position = "absolute";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { syncOverlayHostStacking } from "./overlay-host-stacking";
|
|
5
|
+
|
|
6
|
+
describe("syncOverlayHostStacking", () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
document.body.innerHTML = "";
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("sets position relative on a static element and restores on teardown", () => {
|
|
12
|
+
const host = document.createElement("div");
|
|
13
|
+
document.body.appendChild(host);
|
|
14
|
+
|
|
15
|
+
const teardown = syncOverlayHostStacking(host);
|
|
16
|
+
expect(host.style.position).toBe("relative");
|
|
17
|
+
expect(host.style.zIndex).toBe("100000");
|
|
18
|
+
expect(host.style.isolation).toBe("isolate");
|
|
19
|
+
|
|
20
|
+
teardown();
|
|
21
|
+
expect(host.style.position).toBe("");
|
|
22
|
+
expect(host.style.zIndex).toBe("");
|
|
23
|
+
expect(host.style.isolation).toBe("");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("preserves existing positioned value", () => {
|
|
27
|
+
const host = document.createElement("div");
|
|
28
|
+
host.style.position = "absolute";
|
|
29
|
+
document.body.appendChild(host);
|
|
30
|
+
|
|
31
|
+
const teardown = syncOverlayHostStacking(host);
|
|
32
|
+
expect(host.style.position).toBe("absolute");
|
|
33
|
+
expect(host.style.zIndex).toBe("100000");
|
|
34
|
+
|
|
35
|
+
teardown();
|
|
36
|
+
expect(host.style.position).toBe("absolute");
|
|
37
|
+
expect(host.style.zIndex).toBe("");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("accepts custom z-index", () => {
|
|
41
|
+
const host = document.createElement("div");
|
|
42
|
+
document.body.appendChild(host);
|
|
43
|
+
|
|
44
|
+
const teardown = syncOverlayHostStacking(host, 42);
|
|
45
|
+
expect(host.style.zIndex).toBe("42");
|
|
46
|
+
|
|
47
|
+
teardown();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("restores previous inline z-index on teardown", () => {
|
|
51
|
+
const host = document.createElement("div");
|
|
52
|
+
host.style.zIndex = "5";
|
|
53
|
+
document.body.appendChild(host);
|
|
54
|
+
|
|
55
|
+
const teardown = syncOverlayHostStacking(host);
|
|
56
|
+
expect(host.style.zIndex).toBe("100000");
|
|
57
|
+
|
|
58
|
+
teardown();
|
|
59
|
+
expect(host.style.zIndex).toBe("5");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DEFAULT_OVERLAY_Z_INDEX } from "./constants";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Elevates the light-DOM host element's stacking context so viewport-covering
|
|
5
|
+
* overlays (sidebar, fullscreen) can escape parent stacking traps.
|
|
6
|
+
*
|
|
7
|
+
* - If the host has `position: static`, sets it to `relative` (required for
|
|
8
|
+
* `z-index` to take effect).
|
|
9
|
+
* - Applies `z-index` matching the overlay default.
|
|
10
|
+
* - Applies `isolation: isolate` to create a predictable stacking context.
|
|
11
|
+
*
|
|
12
|
+
* @returns A teardown function that restores only the properties that were changed.
|
|
13
|
+
*/
|
|
14
|
+
export function syncOverlayHostStacking(
|
|
15
|
+
host: HTMLElement,
|
|
16
|
+
zIndex: number = DEFAULT_OVERLAY_Z_INDEX
|
|
17
|
+
): () => void {
|
|
18
|
+
const originalPosition = host.style.position;
|
|
19
|
+
const originalZIndex = host.style.zIndex;
|
|
20
|
+
const originalIsolation = host.style.isolation;
|
|
21
|
+
|
|
22
|
+
const computed = getComputedStyle(host);
|
|
23
|
+
const positionWasSet = computed.position === "static" || computed.position === "";
|
|
24
|
+
if (positionWasSet) {
|
|
25
|
+
host.style.position = "relative";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
host.style.zIndex = String(zIndex);
|
|
29
|
+
host.style.isolation = "isolate";
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
if (positionWasSet) {
|
|
33
|
+
host.style.position = originalPosition;
|
|
34
|
+
}
|
|
35
|
+
host.style.zIndex = originalZIndex;
|
|
36
|
+
host.style.isolation = originalIsolation;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { acquireScrollLock } from "./scroll-lock";
|
|
5
|
+
|
|
6
|
+
describe("acquireScrollLock", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
document.body.style.overflow = "";
|
|
9
|
+
document.body.style.position = "";
|
|
10
|
+
document.body.style.top = "";
|
|
11
|
+
document.body.style.width = "";
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
document.body.style.overflow = "";
|
|
16
|
+
document.body.style.position = "";
|
|
17
|
+
document.body.style.top = "";
|
|
18
|
+
document.body.style.width = "";
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("sets overflow hidden and position fixed on body", () => {
|
|
22
|
+
const release = acquireScrollLock();
|
|
23
|
+
expect(document.body.style.overflow).toBe("hidden");
|
|
24
|
+
expect(document.body.style.position).toBe("fixed");
|
|
25
|
+
expect(document.body.style.width).toBe("100%");
|
|
26
|
+
|
|
27
|
+
release();
|
|
28
|
+
expect(document.body.style.overflow).toBe("");
|
|
29
|
+
expect(document.body.style.position).toBe("");
|
|
30
|
+
expect(document.body.style.width).toBe("");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is ref-counted: first release does not unlock when second acquire is active", () => {
|
|
34
|
+
const release1 = acquireScrollLock();
|
|
35
|
+
const release2 = acquireScrollLock();
|
|
36
|
+
expect(document.body.style.overflow).toBe("hidden");
|
|
37
|
+
|
|
38
|
+
release1();
|
|
39
|
+
expect(document.body.style.overflow).toBe("hidden");
|
|
40
|
+
|
|
41
|
+
release2();
|
|
42
|
+
expect(document.body.style.overflow).toBe("");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("release is idempotent", () => {
|
|
46
|
+
const release = acquireScrollLock();
|
|
47
|
+
release();
|
|
48
|
+
release();
|
|
49
|
+
expect(document.body.style.overflow).toBe("");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("restores original body styles on release", () => {
|
|
53
|
+
document.body.style.overflow = "scroll";
|
|
54
|
+
document.body.style.position = "relative";
|
|
55
|
+
|
|
56
|
+
const release = acquireScrollLock();
|
|
57
|
+
expect(document.body.style.overflow).toBe("hidden");
|
|
58
|
+
expect(document.body.style.position).toBe("fixed");
|
|
59
|
+
|
|
60
|
+
release();
|
|
61
|
+
expect(document.body.style.overflow).toBe("scroll");
|
|
62
|
+
expect(document.body.style.position).toBe("relative");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
interface ScrollLockState {
|
|
2
|
+
originalOverflow: string;
|
|
3
|
+
originalPosition: string;
|
|
4
|
+
originalTop: string;
|
|
5
|
+
originalWidth: string;
|
|
6
|
+
scrollY: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let lockCount = 0;
|
|
10
|
+
let savedState: ScrollLockState | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Acquire a document-level scroll lock. The page body becomes non-scrollable
|
|
14
|
+
* via `overflow: hidden` with an iOS-safe `position: fixed` pattern that
|
|
15
|
+
* preserves the visual scroll position.
|
|
16
|
+
*
|
|
17
|
+
* Ref-counted: multiple callers can acquire; the lock is only released when
|
|
18
|
+
* all callers have released. Each release call is idempotent.
|
|
19
|
+
*
|
|
20
|
+
* @returns A release function. Call it exactly once per acquisition.
|
|
21
|
+
*/
|
|
22
|
+
export function acquireScrollLock(doc: Document = document): () => void {
|
|
23
|
+
lockCount++;
|
|
24
|
+
|
|
25
|
+
if (lockCount === 1) {
|
|
26
|
+
const body = doc.body;
|
|
27
|
+
const win = doc.defaultView ?? window;
|
|
28
|
+
const scrollY = win.scrollY || doc.documentElement.scrollTop;
|
|
29
|
+
|
|
30
|
+
savedState = {
|
|
31
|
+
originalOverflow: body.style.overflow,
|
|
32
|
+
originalPosition: body.style.position,
|
|
33
|
+
originalTop: body.style.top,
|
|
34
|
+
originalWidth: body.style.width,
|
|
35
|
+
scrollY,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
body.style.overflow = "hidden";
|
|
39
|
+
body.style.position = "fixed";
|
|
40
|
+
body.style.top = `-${scrollY}px`;
|
|
41
|
+
body.style.width = "100%";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let released = false;
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
if (released) return;
|
|
48
|
+
released = true;
|
|
49
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
50
|
+
|
|
51
|
+
if (lockCount === 0 && savedState) {
|
|
52
|
+
const body = doc.body;
|
|
53
|
+
const win = doc.defaultView ?? window;
|
|
54
|
+
body.style.overflow = savedState.originalOverflow;
|
|
55
|
+
body.style.position = savedState.originalPosition;
|
|
56
|
+
body.style.top = savedState.originalTop;
|
|
57
|
+
body.style.width = savedState.originalWidth;
|
|
58
|
+
win.scrollTo(0, savedState.scrollY);
|
|
59
|
+
savedState = null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
package/src/utils/theme.test.ts
CHANGED
|
@@ -218,6 +218,40 @@ describe('theme utils', () => {
|
|
|
218
218
|
expect(cssVars['--persona-composer-shadow']).toBe('none');
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
+
it('maps scroll-to-bottom component tokens to dedicated CSS variables', () => {
|
|
222
|
+
const theme = createTheme({
|
|
223
|
+
components: {
|
|
224
|
+
scrollToBottom: {
|
|
225
|
+
background: 'palette.colors.accent.500',
|
|
226
|
+
foreground: 'palette.colors.gray.50',
|
|
227
|
+
border: 'palette.colors.gray.900',
|
|
228
|
+
size: '40px',
|
|
229
|
+
borderRadius: 'palette.radius.full',
|
|
230
|
+
shadow: 'palette.shadows.md',
|
|
231
|
+
padding: '0.5rem 0.875rem',
|
|
232
|
+
gap: '0.5rem',
|
|
233
|
+
fontSize: '0.875rem',
|
|
234
|
+
iconSize: '14px',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
} as any);
|
|
238
|
+
|
|
239
|
+
const cssVars = themeToCssVariables(theme);
|
|
240
|
+
|
|
241
|
+
expect(cssVars['--persona-scroll-to-bottom-bg']).toBe('#06b6d4');
|
|
242
|
+
expect(cssVars['--persona-scroll-to-bottom-fg']).toBe('#f9fafb');
|
|
243
|
+
expect(cssVars['--persona-scroll-to-bottom-border']).toBe('#111827');
|
|
244
|
+
expect(cssVars['--persona-scroll-to-bottom-size']).toBe('40px');
|
|
245
|
+
expect(cssVars['--persona-scroll-to-bottom-radius']).toBe('9999px');
|
|
246
|
+
expect(cssVars['--persona-scroll-to-bottom-shadow']).toBe(
|
|
247
|
+
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
|
|
248
|
+
);
|
|
249
|
+
expect(cssVars['--persona-scroll-to-bottom-padding']).toBe('0.5rem 0.875rem');
|
|
250
|
+
expect(cssVars['--persona-scroll-to-bottom-gap']).toBe('0.5rem');
|
|
251
|
+
expect(cssVars['--persona-scroll-to-bottom-font-size']).toBe('0.875rem');
|
|
252
|
+
expect(cssVars['--persona-scroll-to-bottom-icon-size']).toBe('14px');
|
|
253
|
+
});
|
|
254
|
+
|
|
221
255
|
it('lets config.toolCall.shadow override theme tool bubble shadow on the root element', () => {
|
|
222
256
|
const el = document.createElement('div');
|
|
223
257
|
applyThemeVariables(el, {
|
package/src/utils/tokens.ts
CHANGED
|
@@ -312,6 +312,7 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
|
|
|
312
312
|
border: 'palette.colors.gray.200',
|
|
313
313
|
shadow: 'palette.shadows.sm',
|
|
314
314
|
},
|
|
315
|
+
border: 'semantic.colors.border',
|
|
315
316
|
},
|
|
316
317
|
toolBubble: {
|
|
317
318
|
shadow: 'palette.shadows.sm',
|
|
@@ -334,6 +335,28 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
|
|
|
334
335
|
prose: {
|
|
335
336
|
fontFamily: 'inherit',
|
|
336
337
|
},
|
|
338
|
+
codeBlock: {
|
|
339
|
+
background: 'semantic.colors.container',
|
|
340
|
+
borderColor: 'semantic.colors.border',
|
|
341
|
+
textColor: 'inherit',
|
|
342
|
+
},
|
|
343
|
+
table: {
|
|
344
|
+
headerBackground: 'semantic.colors.container',
|
|
345
|
+
borderColor: 'semantic.colors.border',
|
|
346
|
+
},
|
|
347
|
+
hr: {
|
|
348
|
+
color: 'semantic.colors.divider',
|
|
349
|
+
},
|
|
350
|
+
blockquote: {
|
|
351
|
+
borderColor: 'palette.colors.gray.900',
|
|
352
|
+
background: 'transparent',
|
|
353
|
+
textColor: 'palette.colors.gray.500',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
collapsibleWidget: {
|
|
357
|
+
container: 'palette.colors.gray.50',
|
|
358
|
+
surface: 'semantic.colors.surface',
|
|
359
|
+
border: 'semantic.colors.border',
|
|
337
360
|
},
|
|
338
361
|
voice: {
|
|
339
362
|
recording: {
|
|
@@ -374,6 +397,18 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
|
|
|
374
397
|
border: 'palette.colors.gray.200',
|
|
375
398
|
},
|
|
376
399
|
},
|
|
400
|
+
scrollToBottom: {
|
|
401
|
+
background: 'components.button.primary.background',
|
|
402
|
+
foreground: 'components.button.primary.foreground',
|
|
403
|
+
border: 'semantic.colors.primary',
|
|
404
|
+
size: '40px',
|
|
405
|
+
borderRadius: 'palette.radius.full',
|
|
406
|
+
shadow: 'palette.shadows.sm',
|
|
407
|
+
padding: '0.5rem 0.875rem',
|
|
408
|
+
gap: '0.5rem',
|
|
409
|
+
fontSize: '0.875rem',
|
|
410
|
+
iconSize: '14px',
|
|
411
|
+
},
|
|
377
412
|
artifact: {
|
|
378
413
|
pane: {
|
|
379
414
|
background: 'semantic.colors.container',
|
|
@@ -656,6 +691,7 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
|
|
|
656
691
|
cssVars['--persona-radius-md'] = cssVars['--persona-palette-radius-md'] ?? '0.375rem';
|
|
657
692
|
cssVars['--persona-radius-lg'] = cssVars['--persona-palette-radius-lg'] ?? '0.5rem';
|
|
658
693
|
cssVars['--persona-radius-xl'] = cssVars['--persona-palette-radius-xl'] ?? '0.75rem';
|
|
694
|
+
cssVars['--persona-radius-full'] = cssVars['--persona-palette-radius-full'] ?? '9999px';
|
|
659
695
|
cssVars['--persona-launcher-radius'] =
|
|
660
696
|
cssVars['--persona-components-launcher-borderRadius'] ??
|
|
661
697
|
cssVars['--persona-palette-radius-full'] ??
|
|
@@ -742,6 +778,42 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
|
|
|
742
778
|
cssVars['--persona-components-message-assistant-border'] ?? cssVars['--persona-border'];
|
|
743
779
|
cssVars['--persona-message-assistant-shadow'] =
|
|
744
780
|
cssVars['--persona-components-message-assistant-shadow'] ?? '0 1px 2px 0 rgb(0 0 0 / 0.05)';
|
|
781
|
+
cssVars['--persona-scroll-to-bottom-bg'] =
|
|
782
|
+
cssVars['--persona-components-scrollToBottom-background'] ??
|
|
783
|
+
cssVars['--persona-button-primary-bg'] ??
|
|
784
|
+
cssVars['--persona-accent'];
|
|
785
|
+
cssVars['--persona-scroll-to-bottom-fg'] =
|
|
786
|
+
cssVars['--persona-components-scrollToBottom-foreground'] ??
|
|
787
|
+
cssVars['--persona-button-primary-fg'] ??
|
|
788
|
+
cssVars['--persona-text-inverse'];
|
|
789
|
+
cssVars['--persona-scroll-to-bottom-border'] =
|
|
790
|
+
cssVars['--persona-components-scrollToBottom-border'] ??
|
|
791
|
+
cssVars['--persona-primary'];
|
|
792
|
+
cssVars['--persona-scroll-to-bottom-size'] =
|
|
793
|
+
cssVars['--persona-components-scrollToBottom-size'] ??
|
|
794
|
+
'40px';
|
|
795
|
+
cssVars['--persona-scroll-to-bottom-radius'] =
|
|
796
|
+
cssVars['--persona-components-scrollToBottom-borderRadius'] ??
|
|
797
|
+
cssVars['--persona-button-radius'] ??
|
|
798
|
+
cssVars['--persona-radius-full'] ??
|
|
799
|
+
'9999px';
|
|
800
|
+
cssVars['--persona-scroll-to-bottom-shadow'] =
|
|
801
|
+
cssVars['--persona-components-scrollToBottom-shadow'] ??
|
|
802
|
+
cssVars['--persona-palette-shadows-sm'] ??
|
|
803
|
+
'0 1px 2px 0 rgb(0 0 0 / 0.05)';
|
|
804
|
+
cssVars['--persona-scroll-to-bottom-padding'] =
|
|
805
|
+
cssVars['--persona-components-scrollToBottom-padding'] ??
|
|
806
|
+
'0.5rem 0.875rem';
|
|
807
|
+
cssVars['--persona-scroll-to-bottom-gap'] =
|
|
808
|
+
cssVars['--persona-components-scrollToBottom-gap'] ??
|
|
809
|
+
'0.5rem';
|
|
810
|
+
cssVars['--persona-scroll-to-bottom-font-size'] =
|
|
811
|
+
cssVars['--persona-components-scrollToBottom-fontSize'] ??
|
|
812
|
+
cssVars['--persona-palette-typography-fontSize-sm'] ??
|
|
813
|
+
'0.875rem';
|
|
814
|
+
cssVars['--persona-scroll-to-bottom-icon-size'] =
|
|
815
|
+
cssVars['--persona-components-scrollToBottom-iconSize'] ??
|
|
816
|
+
'14px';
|
|
745
817
|
|
|
746
818
|
cssVars['--persona-tool-bubble-shadow'] =
|
|
747
819
|
cssVars['--persona-components-toolBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
|
|
@@ -774,6 +846,46 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
|
|
|
774
846
|
cssVars['--persona-md-prose-font-family'] = mdProseFont;
|
|
775
847
|
}
|
|
776
848
|
|
|
849
|
+
// Markdown code block
|
|
850
|
+
cssVars['--persona-md-code-block-bg'] =
|
|
851
|
+
cssVars['--persona-components-markdown-codeBlock-background'] ?? cssVars['--persona-container'];
|
|
852
|
+
cssVars['--persona-md-code-block-border-color'] =
|
|
853
|
+
cssVars['--persona-components-markdown-codeBlock-borderColor'] ?? cssVars['--persona-border'];
|
|
854
|
+
cssVars['--persona-md-code-block-text-color'] =
|
|
855
|
+
cssVars['--persona-components-markdown-codeBlock-textColor'] ?? 'inherit';
|
|
856
|
+
|
|
857
|
+
// Markdown table
|
|
858
|
+
cssVars['--persona-md-table-header-bg'] =
|
|
859
|
+
cssVars['--persona-components-markdown-table-headerBackground'] ?? cssVars['--persona-container'];
|
|
860
|
+
cssVars['--persona-md-table-border-color'] =
|
|
861
|
+
cssVars['--persona-components-markdown-table-borderColor'] ?? cssVars['--persona-border'];
|
|
862
|
+
|
|
863
|
+
// Markdown HR
|
|
864
|
+
cssVars['--persona-md-hr-color'] =
|
|
865
|
+
cssVars['--persona-components-markdown-hr-color'] ?? cssVars['--persona-divider'];
|
|
866
|
+
|
|
867
|
+
// Markdown blockquote
|
|
868
|
+
cssVars['--persona-md-blockquote-border-color'] =
|
|
869
|
+
cssVars['--persona-components-markdown-blockquote-borderColor'] ??
|
|
870
|
+
cssVars['--persona-palette-colors-gray-900'];
|
|
871
|
+
cssVars['--persona-md-blockquote-bg'] =
|
|
872
|
+
cssVars['--persona-components-markdown-blockquote-background'] ?? 'transparent';
|
|
873
|
+
cssVars['--persona-md-blockquote-text-color'] =
|
|
874
|
+
cssVars['--persona-components-markdown-blockquote-textColor'] ??
|
|
875
|
+
cssVars['--persona-palette-colors-gray-500'];
|
|
876
|
+
|
|
877
|
+
// Collapsible widget chrome (tool/reasoning/approval bubbles)
|
|
878
|
+
cssVars['--cw-container'] =
|
|
879
|
+
cssVars['--persona-components-collapsibleWidget-container'] ?? cssVars['--persona-surface'];
|
|
880
|
+
cssVars['--cw-surface'] =
|
|
881
|
+
cssVars['--persona-components-collapsibleWidget-surface'] ?? cssVars['--persona-surface'];
|
|
882
|
+
cssVars['--cw-border'] =
|
|
883
|
+
cssVars['--persona-components-collapsibleWidget-border'] ?? cssVars['--persona-border'];
|
|
884
|
+
|
|
885
|
+
// Message border
|
|
886
|
+
cssVars['--persona-message-border'] =
|
|
887
|
+
cssVars['--persona-components-message-border'] ?? cssVars['--persona-border'];
|
|
888
|
+
|
|
777
889
|
// Icon button tokens
|
|
778
890
|
const components = theme.components;
|
|
779
891
|
const iconBtn = components?.iconButton;
|