@runtypelabs/persona 1.47.0 → 2.0.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 +140 -8
- package/dist/index.cjs +90 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1093 -25
- package/dist/index.d.ts +1093 -25
- package/dist/index.global.js +111 -60
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +90 -39
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/widget.css +852 -505
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +80 -0
- package/src/client.test.ts +20 -21
- package/src/client.ts +153 -4
- package/src/components/approval-bubble.ts +45 -42
- package/src/components/artifact-card.ts +91 -0
- package/src/components/artifact-pane.ts +501 -0
- package/src/components/composer-builder.ts +32 -27
- package/src/components/event-stream-view.ts +40 -40
- package/src/components/feedback.ts +36 -36
- package/src/components/forms.ts +11 -11
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +55 -36
- package/src/components/header-layouts.ts +58 -125
- package/src/components/launcher.ts +36 -21
- package/src/components/message-bubble.ts +92 -65
- package/src/components/messages.ts +2 -2
- package/src/components/panel.ts +42 -11
- package/src/components/reasoning-bubble.ts +23 -23
- package/src/components/registry.ts +4 -0
- package/src/components/suggestions.ts +1 -1
- package/src/components/tool-bubble.ts +32 -32
- package/src/defaults.ts +30 -4
- package/src/index.ts +80 -2
- package/src/install.ts +22 -0
- package/src/plugins/types.ts +23 -0
- package/src/postprocessors.ts +2 -2
- package/src/runtime/host-layout.ts +174 -0
- package/src/runtime/init.test.ts +236 -0
- package/src/runtime/init.ts +114 -55
- package/src/session.ts +173 -7
- package/src/styles/tailwind.css +1 -1
- package/src/styles/widget.css +852 -505
- package/src/types/theme.ts +354 -0
- package/src/types.ts +348 -16
- package/src/ui.docked.test.ts +104 -0
- package/src/ui.ts +1093 -244
- package/src/utils/artifact-gate.test.ts +255 -0
- package/src/utils/artifact-gate.ts +142 -0
- package/src/utils/artifact-resize.test.ts +64 -0
- package/src/utils/artifact-resize.ts +67 -0
- package/src/utils/attachment-manager.ts +10 -10
- package/src/utils/code-generators.test.ts +52 -0
- package/src/utils/code-generators.ts +40 -36
- package/src/utils/dock.ts +17 -0
- package/src/utils/dom-context.test.ts +504 -0
- package/src/utils/dom-context.ts +896 -0
- package/src/utils/dom.ts +12 -1
- package/src/utils/message-fingerprint.test.ts +187 -0
- package/src/utils/message-fingerprint.ts +105 -0
- package/src/utils/migration.ts +179 -0
- package/src/utils/morph.ts +1 -1
- package/src/utils/plugins.ts +175 -0
- package/src/utils/positioning.ts +4 -4
- package/src/utils/theme.test.ts +125 -0
- package/src/utils/theme.ts +216 -60
- package/src/utils/tokens.ts +682 -0
- package/src/voice/audio-playback-manager.ts +187 -0
- package/src/voice/runtype-voice-provider.ts +305 -69
- package/src/voice/voice-activity-detector.ts +90 -0
- package/src/voice/voice.test.ts +6 -5
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { createTheme, getActiveTheme, themeToCssVariables } from './theme';
|
|
5
|
+
|
|
6
|
+
describe('theme utils', () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
document.documentElement.classList.remove('dark');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('uses darkTheme overrides when dark mode is active', () => {
|
|
12
|
+
const lightAndDarkThemeConfig = {
|
|
13
|
+
colorScheme: 'dark' as const,
|
|
14
|
+
theme: {
|
|
15
|
+
primary: '#111111',
|
|
16
|
+
},
|
|
17
|
+
darkTheme: {
|
|
18
|
+
primary: '#22c55e',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const activeTheme = getActiveTheme(lightAndDarkThemeConfig as any);
|
|
23
|
+
const cssVars = themeToCssVariables(activeTheme);
|
|
24
|
+
|
|
25
|
+
expect(cssVars['--persona-palette-colors-primary-500']).toBe('#22c55e');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('uses darkTheme overrides after auto-detecting dark mode', () => {
|
|
29
|
+
document.documentElement.classList.add('dark');
|
|
30
|
+
|
|
31
|
+
const lightAndDarkThemeConfig = {
|
|
32
|
+
colorScheme: 'auto' as const,
|
|
33
|
+
theme: {
|
|
34
|
+
primary: '#111111',
|
|
35
|
+
},
|
|
36
|
+
darkTheme: {
|
|
37
|
+
primary: '#22c55e',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const activeTheme = getActiveTheme(lightAndDarkThemeConfig as any);
|
|
42
|
+
const cssVars = themeToCssVariables(activeTheme);
|
|
43
|
+
|
|
44
|
+
expect(cssVars['--persona-palette-colors-primary-500']).toBe('#22c55e');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('maps radius tokens into the legacy widget radius aliases', () => {
|
|
48
|
+
const theme = createTheme({
|
|
49
|
+
palette: {
|
|
50
|
+
radius: {
|
|
51
|
+
none: '0px',
|
|
52
|
+
sm: '2px',
|
|
53
|
+
md: '6px',
|
|
54
|
+
lg: '10px',
|
|
55
|
+
xl: '18px',
|
|
56
|
+
full: '9999px',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
components: {
|
|
60
|
+
panel: {
|
|
61
|
+
borderRadius: 'palette.radius.xl',
|
|
62
|
+
},
|
|
63
|
+
input: {
|
|
64
|
+
borderRadius: 'palette.radius.md',
|
|
65
|
+
},
|
|
66
|
+
launcher: {
|
|
67
|
+
borderRadius: 'palette.radius.full',
|
|
68
|
+
},
|
|
69
|
+
button: {
|
|
70
|
+
primary: {
|
|
71
|
+
borderRadius: 'palette.radius.md',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
message: {
|
|
75
|
+
user: {
|
|
76
|
+
borderRadius: 'palette.radius.sm',
|
|
77
|
+
},
|
|
78
|
+
assistant: {
|
|
79
|
+
borderRadius: 'palette.radius.lg',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
} as any);
|
|
84
|
+
|
|
85
|
+
const cssVars = themeToCssVariables(theme);
|
|
86
|
+
|
|
87
|
+
expect(cssVars['--persona-radius-sm']).toBe('2px');
|
|
88
|
+
expect(cssVars['--persona-radius-md']).toBe('6px');
|
|
89
|
+
expect(cssVars['--persona-radius-lg']).toBe('10px');
|
|
90
|
+
expect(cssVars['--persona-panel-radius']).toBe('18px');
|
|
91
|
+
expect(cssVars['--persona-input-radius']).toBe('6px');
|
|
92
|
+
expect(cssVars['--persona-message-user-radius']).toBe('2px');
|
|
93
|
+
expect(cssVars['--persona-message-assistant-radius']).toBe('10px');
|
|
94
|
+
expect(cssVars['--persona-launcher-radius']).toBe('9999px');
|
|
95
|
+
expect(cssVars['--persona-button-radius']).toBe('6px');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('maps markdown link and optional heading tokens to consumer CSS vars', () => {
|
|
99
|
+
const theme = createTheme({
|
|
100
|
+
components: {
|
|
101
|
+
markdown: {
|
|
102
|
+
link: {
|
|
103
|
+
foreground: '#60a5fa',
|
|
104
|
+
},
|
|
105
|
+
prose: {
|
|
106
|
+
fontFamily: 'Georgia, serif',
|
|
107
|
+
},
|
|
108
|
+
heading: {
|
|
109
|
+
h1: { fontSize: '1.375rem', fontWeight: '650' },
|
|
110
|
+
h2: { fontSize: '1.125rem', fontWeight: '600' },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
} as any);
|
|
115
|
+
|
|
116
|
+
const cssVars = themeToCssVariables(theme);
|
|
117
|
+
|
|
118
|
+
expect(cssVars['--persona-md-link-color']).toBe('#60a5fa');
|
|
119
|
+
expect(cssVars['--persona-md-h1-size']).toBe('1.375rem');
|
|
120
|
+
expect(cssVars['--persona-md-h1-weight']).toBe('650');
|
|
121
|
+
expect(cssVars['--persona-md-h2-size']).toBe('1.125rem');
|
|
122
|
+
expect(cssVars['--persona-md-h2-weight']).toBe('600');
|
|
123
|
+
expect(cssVars['--persona-md-prose-font-family']).toBe('Georgia, serif');
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/utils/theme.ts
CHANGED
|
@@ -1,105 +1,261 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PersonaTheme } from '../types/theme';
|
|
2
|
+
import type { AgentWidgetConfig, AgentWidgetTheme } from '../types';
|
|
3
|
+
import { createTheme, resolveTokens, themeToCssVariables } from './tokens';
|
|
4
|
+
import { migrateV1Theme } from './migration';
|
|
5
|
+
|
|
6
|
+
export type ColorScheme = 'light' | 'dark' | 'auto';
|
|
7
|
+
|
|
8
|
+
export interface PersonaWidgetConfig {
|
|
9
|
+
theme?: Partial<PersonaTheme>;
|
|
10
|
+
darkTheme?: Partial<PersonaTheme>;
|
|
11
|
+
colorScheme?: ColorScheme;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type WidgetConfig = PersonaWidgetConfig | AgentWidgetConfig;
|
|
15
|
+
|
|
16
|
+
const DARK_PALETTE = {
|
|
17
|
+
colors: {
|
|
18
|
+
primary: {
|
|
19
|
+
50: '#eff6ff',
|
|
20
|
+
100: '#dbeafe',
|
|
21
|
+
200: '#bfdbfe',
|
|
22
|
+
300: '#93c5fd',
|
|
23
|
+
400: '#60a5fa',
|
|
24
|
+
500: '#3b82f6',
|
|
25
|
+
600: '#2563eb',
|
|
26
|
+
700: '#1d4ed8',
|
|
27
|
+
800: '#1e40af',
|
|
28
|
+
900: '#1e3a8a',
|
|
29
|
+
950: '#172554',
|
|
30
|
+
},
|
|
31
|
+
secondary: {
|
|
32
|
+
50: '#f5f3ff',
|
|
33
|
+
100: '#ede9fe',
|
|
34
|
+
200: '#ddd6fe',
|
|
35
|
+
300: '#c4b5fd',
|
|
36
|
+
400: '#a78bfa',
|
|
37
|
+
500: '#8b5cf6',
|
|
38
|
+
600: '#7c3aed',
|
|
39
|
+
700: '#6d28d9',
|
|
40
|
+
800: '#5b21b6',
|
|
41
|
+
900: '#4c1d95',
|
|
42
|
+
950: '#2e1065',
|
|
43
|
+
},
|
|
44
|
+
accent: {
|
|
45
|
+
50: '#ecfeff',
|
|
46
|
+
100: '#cffafe',
|
|
47
|
+
200: '#a5f3fc',
|
|
48
|
+
300: '#67e8f9',
|
|
49
|
+
400: '#22d3ee',
|
|
50
|
+
500: '#06b6d4',
|
|
51
|
+
600: '#0891b2',
|
|
52
|
+
700: '#0e7490',
|
|
53
|
+
800: '#155e75',
|
|
54
|
+
900: '#164e63',
|
|
55
|
+
950: '#083344',
|
|
56
|
+
},
|
|
57
|
+
gray: {
|
|
58
|
+
50: '#f9fafb',
|
|
59
|
+
100: '#f3f4f6',
|
|
60
|
+
200: '#e5e7eb',
|
|
61
|
+
300: '#d1d5db',
|
|
62
|
+
400: '#9ca3af',
|
|
63
|
+
500: '#6b7280',
|
|
64
|
+
600: '#4b5563',
|
|
65
|
+
700: '#374151',
|
|
66
|
+
800: '#1f2937',
|
|
67
|
+
900: '#111827',
|
|
68
|
+
950: '#030712',
|
|
69
|
+
},
|
|
70
|
+
success: {
|
|
71
|
+
50: '#f0fdf4',
|
|
72
|
+
100: '#dcfce7',
|
|
73
|
+
200: '#bbf7d0',
|
|
74
|
+
300: '#86efac',
|
|
75
|
+
400: '#4ade80',
|
|
76
|
+
500: '#22c55e',
|
|
77
|
+
600: '#16a34a',
|
|
78
|
+
700: '#15803d',
|
|
79
|
+
800: '#166534',
|
|
80
|
+
900: '#14532d',
|
|
81
|
+
},
|
|
82
|
+
warning: {
|
|
83
|
+
50: '#fefce8',
|
|
84
|
+
100: '#fef9c3',
|
|
85
|
+
200: '#fef08a',
|
|
86
|
+
300: '#fde047',
|
|
87
|
+
400: '#facc15',
|
|
88
|
+
500: '#eab308',
|
|
89
|
+
600: '#ca8a04',
|
|
90
|
+
700: '#a16207',
|
|
91
|
+
800: '#854d0e',
|
|
92
|
+
900: '#713f12',
|
|
93
|
+
},
|
|
94
|
+
error: {
|
|
95
|
+
50: '#fef2f2',
|
|
96
|
+
100: '#fee2e2',
|
|
97
|
+
200: '#fecaca',
|
|
98
|
+
300: '#fca5a5',
|
|
99
|
+
400: '#f87171',
|
|
100
|
+
500: '#ef4444',
|
|
101
|
+
600: '#dc2626',
|
|
102
|
+
700: '#b91c1c',
|
|
103
|
+
800: '#991b1b',
|
|
104
|
+
900: '#7f1d1d',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
110
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
111
|
+
|
|
112
|
+
const deepMerge = <T extends Record<string, unknown>>(
|
|
113
|
+
base: T | undefined,
|
|
114
|
+
override: Record<string, unknown> | undefined
|
|
115
|
+
): T | Record<string, unknown> | undefined => {
|
|
116
|
+
if (!base) return override;
|
|
117
|
+
if (!override) return base;
|
|
118
|
+
|
|
119
|
+
const merged: Record<string, unknown> = { ...base };
|
|
120
|
+
|
|
121
|
+
for (const [key, value] of Object.entries(override)) {
|
|
122
|
+
const existing = merged[key];
|
|
123
|
+
if (isObject(existing) && isObject(value)) {
|
|
124
|
+
merged[key] = deepMerge(existing, value);
|
|
125
|
+
} else {
|
|
126
|
+
merged[key] = value;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return merged;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const isTokenTheme = (theme: unknown): theme is Partial<PersonaTheme> => {
|
|
134
|
+
return isObject(theme) && ('palette' in theme || 'semantic' in theme || 'components' in theme);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const normalizeThemeConfig = (
|
|
138
|
+
theme: Partial<PersonaTheme> | AgentWidgetTheme | undefined
|
|
139
|
+
): Partial<PersonaTheme> | undefined => {
|
|
140
|
+
if (!theme) return undefined;
|
|
141
|
+
const migratedTheme = migrateV1Theme(theme as AgentWidgetTheme, { warn: false });
|
|
142
|
+
if (isTokenTheme(theme)) {
|
|
143
|
+
return deepMerge(migratedTheme, theme) as Partial<PersonaTheme>;
|
|
144
|
+
}
|
|
145
|
+
return migratedTheme;
|
|
146
|
+
};
|
|
2
147
|
|
|
3
|
-
/**
|
|
4
|
-
* Detects the current color scheme from the page.
|
|
5
|
-
* 1. Checks if <html> element has 'dark' class
|
|
6
|
-
* 2. Falls back to prefers-color-scheme media query
|
|
7
|
-
*/
|
|
8
148
|
export const detectColorScheme = (): 'light' | 'dark' => {
|
|
9
|
-
// Check for 'dark' class on <html> element
|
|
10
149
|
if (typeof document !== 'undefined' && document.documentElement.classList.contains('dark')) {
|
|
11
150
|
return 'dark';
|
|
12
151
|
}
|
|
13
|
-
|
|
14
|
-
// Fall back to media query
|
|
152
|
+
|
|
15
153
|
if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
|
|
16
154
|
return 'dark';
|
|
17
155
|
}
|
|
18
|
-
|
|
156
|
+
|
|
19
157
|
return 'light';
|
|
20
158
|
};
|
|
21
159
|
|
|
22
|
-
|
|
23
|
-
* Gets the active theme based on colorScheme setting and current detection.
|
|
24
|
-
*/
|
|
25
|
-
export const getActiveTheme = (config?: AgentWidgetConfig): AgentWidgetTheme => {
|
|
160
|
+
const getColorSchemeFromConfig = (config?: WidgetConfig): 'light' | 'dark' => {
|
|
26
161
|
const colorScheme = config?.colorScheme ?? 'light';
|
|
27
|
-
|
|
28
|
-
|
|
162
|
+
|
|
163
|
+
if (colorScheme === 'light') return 'light';
|
|
164
|
+
if (colorScheme === 'dark') return 'dark';
|
|
165
|
+
|
|
166
|
+
return detectColorScheme();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const getColorScheme = (config?: WidgetConfig): 'light' | 'dark' => {
|
|
170
|
+
return getColorSchemeFromConfig(config);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const createLightTheme = (userConfig?: Partial<PersonaTheme>): PersonaTheme => {
|
|
174
|
+
return createTheme(userConfig);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const createDarkTheme = (userConfig?: Partial<PersonaTheme>): PersonaTheme => {
|
|
178
|
+
const baseTheme = createTheme(undefined, { validate: false });
|
|
29
179
|
|
|
30
|
-
|
|
31
|
-
|
|
180
|
+
return createTheme(
|
|
181
|
+
{
|
|
182
|
+
...userConfig,
|
|
183
|
+
palette: {
|
|
184
|
+
...baseTheme.palette,
|
|
185
|
+
colors: {
|
|
186
|
+
...DARK_PALETTE.colors,
|
|
187
|
+
...userConfig?.palette?.colors,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{ validate: false }
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const getActiveTheme = (config?: WidgetConfig): PersonaTheme => {
|
|
196
|
+
const scheme = getColorScheme(config);
|
|
197
|
+
const lightThemeConfig = normalizeThemeConfig(config?.theme as Partial<PersonaTheme> | AgentWidgetTheme | undefined);
|
|
198
|
+
const darkThemeConfig = normalizeThemeConfig(config?.darkTheme as Partial<PersonaTheme> | AgentWidgetTheme | undefined);
|
|
199
|
+
|
|
200
|
+
if (scheme === 'dark') {
|
|
201
|
+
return createDarkTheme(
|
|
202
|
+
deepMerge(lightThemeConfig, darkThemeConfig) as Partial<PersonaTheme> | undefined
|
|
203
|
+
);
|
|
32
204
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
205
|
+
|
|
206
|
+
return createLightTheme(lightThemeConfig);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const getCssVariables = (theme: PersonaTheme): Record<string, string> => {
|
|
210
|
+
return themeToCssVariables(theme);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const applyThemeVariables = (
|
|
214
|
+
element: HTMLElement,
|
|
215
|
+
config?: WidgetConfig
|
|
216
|
+
): void => {
|
|
217
|
+
const theme = getActiveTheme(config);
|
|
218
|
+
const cssVars = getCssVariables(theme);
|
|
219
|
+
|
|
220
|
+
for (const [name, value] of Object.entries(cssVars)) {
|
|
221
|
+
element.style.setProperty(name, value);
|
|
36
222
|
}
|
|
37
|
-
|
|
38
|
-
// colorScheme === 'auto'
|
|
39
|
-
const detectedScheme = detectColorScheme();
|
|
40
|
-
return detectedScheme === 'dark' ? darkTheme : lightTheme;
|
|
41
223
|
};
|
|
42
224
|
|
|
43
|
-
/**
|
|
44
|
-
* Creates observers for theme changes (HTML class and media query).
|
|
45
|
-
* Returns a cleanup function.
|
|
46
|
-
*/
|
|
47
225
|
export const createThemeObserver = (
|
|
48
226
|
callback: (scheme: 'light' | 'dark') => void
|
|
49
227
|
): (() => void) => {
|
|
50
228
|
const cleanupFns: Array<() => void> = [];
|
|
51
|
-
|
|
52
|
-
// Observe HTML class changes
|
|
229
|
+
|
|
53
230
|
if (typeof document !== 'undefined' && typeof MutationObserver !== 'undefined') {
|
|
54
231
|
const observer = new MutationObserver(() => {
|
|
55
232
|
callback(detectColorScheme());
|
|
56
233
|
});
|
|
57
|
-
|
|
234
|
+
|
|
58
235
|
observer.observe(document.documentElement, {
|
|
59
236
|
attributes: true,
|
|
60
|
-
attributeFilter: ['class']
|
|
237
|
+
attributeFilter: ['class'],
|
|
61
238
|
});
|
|
62
|
-
|
|
239
|
+
|
|
63
240
|
cleanupFns.push(() => observer.disconnect());
|
|
64
241
|
}
|
|
65
|
-
|
|
66
|
-
// Observe media query changes
|
|
242
|
+
|
|
67
243
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
68
244
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
69
245
|
const handleChange = () => callback(detectColorScheme());
|
|
70
|
-
|
|
71
|
-
// Use addEventListener if available (modern browsers), otherwise addListener
|
|
246
|
+
|
|
72
247
|
if (mediaQuery.addEventListener) {
|
|
73
248
|
mediaQuery.addEventListener('change', handleChange);
|
|
74
249
|
cleanupFns.push(() => mediaQuery.removeEventListener('change', handleChange));
|
|
75
250
|
} else if (mediaQuery.addListener) {
|
|
76
|
-
// Legacy Safari
|
|
77
251
|
mediaQuery.addListener(handleChange);
|
|
78
252
|
cleanupFns.push(() => mediaQuery.removeListener(handleChange));
|
|
79
253
|
}
|
|
80
254
|
}
|
|
81
|
-
|
|
255
|
+
|
|
82
256
|
return () => {
|
|
83
|
-
cleanupFns.forEach(fn => fn());
|
|
257
|
+
cleanupFns.forEach((fn) => fn());
|
|
84
258
|
};
|
|
85
259
|
};
|
|
86
260
|
|
|
87
|
-
export
|
|
88
|
-
element: HTMLElement,
|
|
89
|
-
config?: AgentWidgetConfig
|
|
90
|
-
) => {
|
|
91
|
-
const theme = getActiveTheme(config);
|
|
92
|
-
Object.entries(theme).forEach(([key, value]) => {
|
|
93
|
-
// Skip undefined or empty values
|
|
94
|
-
if (value === undefined || value === null || value === "") {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
// Convert camelCase to kebab-case (e.g., radiusSm → radius-sm)
|
|
98
|
-
const kebabKey = key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
99
|
-
element.style.setProperty(`--cw-${kebabKey}`, String(value));
|
|
100
|
-
});
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
261
|
+
export { createTheme, resolveTokens, themeToCssVariables };
|