@runtypelabs/persona 3.5.2 → 3.7.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 +46 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.global.js +70 -70
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -46
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +18015 -0
- package/dist/theme-editor.d.cts +3888 -0
- package/dist/theme-editor.d.ts +3888 -0
- package/dist/theme-editor.js +17909 -0
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +33 -0
- package/dist/theme-reference.d.ts +33 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +69 -25
- package/package.json +9 -7
- package/src/components/artifact-card.ts +1 -1
- package/src/components/composer-builder.ts +16 -29
- package/src/components/demo-carousel.ts +5 -5
- package/src/components/event-stream-view.test.ts +142 -0
- package/src/components/event-stream-view.ts +68 -29
- package/src/components/header-builder.ts +2 -2
- package/src/components/launcher.ts +9 -0
- package/src/components/message-bubble.ts +9 -3
- package/src/components/suggestions.ts +1 -1
- package/src/defaults.ts +24 -9
- package/src/scroll-to-bottom-defaults.test.ts +13 -0
- package/src/styles/widget.css +69 -25
- package/src/theme-editor/color-utils.ts +252 -0
- package/src/theme-editor/index.ts +131 -0
- package/src/theme-editor/presets.ts +144 -0
- package/src/theme-editor/preview-utils.ts +265 -0
- package/src/theme-editor/preview.ts +445 -0
- package/src/theme-editor/role-mappings.ts +343 -0
- package/src/theme-editor/sections.test.ts +43 -0
- package/src/theme-editor/sections.ts +994 -0
- package/src/theme-editor/state.ts +298 -0
- package/src/theme-editor/types.ts +177 -0
- package/src/theme-editor.ts +2 -0
- package/src/theme-reference.ts +8 -0
- package/src/types/theme.ts +11 -0
- package/src/types.ts +22 -0
- package/src/ui.scroll.test.ts +554 -0
- package/src/ui.ts +223 -133
- package/src/utils/auto-follow.test.ts +110 -0
- package/src/utils/auto-follow.ts +112 -0
- package/src/utils/plugins.ts +1 -1
- package/src/utils/theme.test.ts +44 -8
- package/src/utils/theme.ts +11 -11
- package/src/utils/tokens.ts +137 -41
- package/widget.css +0 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface Role → Token mapping layer.
|
|
3
|
+
*
|
|
4
|
+
* Maps high-level editor choices (family + intensity) to concrete palette
|
|
5
|
+
* token references across multiple component/semantic paths. This is the
|
|
6
|
+
* core of the "Interface Roles" editor section — one picker writes to
|
|
7
|
+
* many tokens atomically.
|
|
8
|
+
*
|
|
9
|
+
* All functions are pure and headless (no DOM).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { RoleTarget, RoleIntensity, RoleAssignmentOptions } from './types';
|
|
13
|
+
|
|
14
|
+
// ─── Intensities ────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export const ROLE_INTENSITIES: RoleIntensity[] = [
|
|
17
|
+
{ id: 'solid', label: 'Solid' },
|
|
18
|
+
{ id: 'soft', label: 'Soft' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// ─── Palette families available for role assignment ─────────────
|
|
22
|
+
|
|
23
|
+
export const ROLE_FAMILIES = ['primary', 'secondary', 'accent', 'gray'] as const;
|
|
24
|
+
export type RoleFamily = (typeof ROLE_FAMILIES)[number];
|
|
25
|
+
|
|
26
|
+
/** Display labels for palette families in the editor */
|
|
27
|
+
export const ROLE_FAMILY_LABELS: Record<RoleFamily, string> = {
|
|
28
|
+
primary: 'Primary',
|
|
29
|
+
secondary: 'Secondary',
|
|
30
|
+
accent: 'Accent',
|
|
31
|
+
gray: 'Neutral',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ─── Role Definitions ───────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export const ROLE_SURFACES: RoleAssignmentOptions = {
|
|
37
|
+
roleId: 'role-surfaces',
|
|
38
|
+
helper: 'Page and panel backgrounds',
|
|
39
|
+
previewZone: 'container',
|
|
40
|
+
intensities: ROLE_INTENSITIES,
|
|
41
|
+
targets: [
|
|
42
|
+
{ path: 'semantic.colors.background', kind: 'background' },
|
|
43
|
+
{ path: 'semantic.colors.surface', kind: 'background' },
|
|
44
|
+
{ path: 'semantic.colors.container', kind: 'background' },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const ROLE_HEADER: RoleAssignmentOptions = {
|
|
49
|
+
roleId: 'role-header',
|
|
50
|
+
helper: 'Widget header bar',
|
|
51
|
+
previewZone: 'header',
|
|
52
|
+
intensities: ROLE_INTENSITIES,
|
|
53
|
+
targets: [
|
|
54
|
+
{ path: 'components.header.background', kind: 'background' },
|
|
55
|
+
{ path: 'components.header.border', kind: 'border' },
|
|
56
|
+
{ path: 'components.header.iconBackground', kind: 'accent' },
|
|
57
|
+
{ path: 'components.header.iconForeground', kind: 'foreground' },
|
|
58
|
+
{ path: 'components.header.titleForeground', kind: 'accent' },
|
|
59
|
+
{ path: 'components.header.subtitleForeground', kind: 'foreground' },
|
|
60
|
+
{ path: 'components.header.actionIconForeground', kind: 'foreground' },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const ROLE_USER_MESSAGES: RoleAssignmentOptions = {
|
|
65
|
+
roleId: 'role-user-messages',
|
|
66
|
+
helper: 'User chat bubbles',
|
|
67
|
+
previewZone: 'user-message',
|
|
68
|
+
intensities: ROLE_INTENSITIES,
|
|
69
|
+
targets: [
|
|
70
|
+
{ path: 'components.message.user.background', kind: 'background' },
|
|
71
|
+
{ path: 'components.message.user.text', kind: 'foreground' },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const ROLE_ASSISTANT_MESSAGES: RoleAssignmentOptions = {
|
|
76
|
+
roleId: 'role-assistant-messages',
|
|
77
|
+
helper: 'Assistant chat bubbles',
|
|
78
|
+
previewZone: 'assistant-message',
|
|
79
|
+
intensities: ROLE_INTENSITIES,
|
|
80
|
+
targets: [
|
|
81
|
+
{ path: 'components.message.assistant.background', kind: 'background' },
|
|
82
|
+
{ path: 'components.message.assistant.text', kind: 'foreground' },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const ROLE_PRIMARY_ACTIONS: RoleAssignmentOptions = {
|
|
87
|
+
roleId: 'role-primary-actions',
|
|
88
|
+
helper: 'Send button and primary buttons',
|
|
89
|
+
intensities: ROLE_INTENSITIES,
|
|
90
|
+
targets: [
|
|
91
|
+
{ path: 'components.button.primary.background', kind: 'background' },
|
|
92
|
+
{ path: 'components.button.primary.foreground', kind: 'foreground' },
|
|
93
|
+
{ path: 'semantic.colors.interactive.default', kind: 'accent' },
|
|
94
|
+
{ path: 'semantic.colors.interactive.hover', kind: 'accent' },
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const ROLE_SCROLL_TO_BOTTOM: RoleAssignmentOptions = {
|
|
99
|
+
roleId: 'role-scroll-to-bottom',
|
|
100
|
+
helper: 'Scroll-to-bottom affordances in transcript and event stream',
|
|
101
|
+
intensities: ROLE_INTENSITIES,
|
|
102
|
+
targets: [
|
|
103
|
+
{ path: 'components.scrollToBottom.background', kind: 'background' },
|
|
104
|
+
{ path: 'components.scrollToBottom.foreground', kind: 'foreground' },
|
|
105
|
+
{ path: 'components.scrollToBottom.border', kind: 'border' },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const ROLE_INPUT: RoleAssignmentOptions = {
|
|
110
|
+
roleId: 'role-input',
|
|
111
|
+
helper: 'Message input field',
|
|
112
|
+
previewZone: 'composer',
|
|
113
|
+
intensities: ROLE_INTENSITIES,
|
|
114
|
+
targets: [
|
|
115
|
+
{ path: 'components.input.background', kind: 'background' },
|
|
116
|
+
{ path: 'components.input.placeholder', kind: 'foreground' },
|
|
117
|
+
{ path: 'components.input.focus.border', kind: 'accent' },
|
|
118
|
+
{ path: 'components.input.focus.ring', kind: 'accent' },
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const ROLE_LINKS_FOCUS: RoleAssignmentOptions = {
|
|
123
|
+
roleId: 'role-links-focus',
|
|
124
|
+
helper: 'Links, focus rings, and interactive highlights',
|
|
125
|
+
intensities: ROLE_INTENSITIES,
|
|
126
|
+
targets: [
|
|
127
|
+
{ path: 'semantic.colors.accent', kind: 'accent' },
|
|
128
|
+
{ path: 'semantic.colors.interactive.focus', kind: 'accent' },
|
|
129
|
+
{ path: 'semantic.colors.interactive.active', kind: 'accent' },
|
|
130
|
+
{ path: 'components.markdown.link.foreground', kind: 'accent' },
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const ROLE_BORDERS: RoleAssignmentOptions = {
|
|
135
|
+
roleId: 'role-borders',
|
|
136
|
+
helper: 'Borders, dividers, and separators',
|
|
137
|
+
intensities: ROLE_INTENSITIES,
|
|
138
|
+
targets: [
|
|
139
|
+
{ path: 'semantic.colors.border', kind: 'border' },
|
|
140
|
+
{ path: 'semantic.colors.divider', kind: 'border' },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** All interface role definitions in display order */
|
|
145
|
+
export const ALL_ROLES: RoleAssignmentOptions[] = [
|
|
146
|
+
ROLE_SURFACES,
|
|
147
|
+
ROLE_HEADER,
|
|
148
|
+
ROLE_USER_MESSAGES,
|
|
149
|
+
ROLE_ASSISTANT_MESSAGES,
|
|
150
|
+
ROLE_PRIMARY_ACTIONS,
|
|
151
|
+
ROLE_SCROLL_TO_BOTTOM,
|
|
152
|
+
ROLE_INPUT,
|
|
153
|
+
ROLE_LINKS_FOCUS,
|
|
154
|
+
ROLE_BORDERS,
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// ─── Resolution ─────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolve a role assignment (family + intensity) into concrete token writes.
|
|
161
|
+
*
|
|
162
|
+
* Returns a map of `{ "theme.{path}": "palette.colors.{family}.{shade}" }`.
|
|
163
|
+
* The `theme.` prefix is added so callers can pass the result directly to
|
|
164
|
+
* `state.setBatch()`.
|
|
165
|
+
*/
|
|
166
|
+
export function resolveRoleAssignment(
|
|
167
|
+
family: string,
|
|
168
|
+
intensity: string,
|
|
169
|
+
role: RoleAssignmentOptions
|
|
170
|
+
): Record<string, string> {
|
|
171
|
+
const writes: Record<string, string> = {};
|
|
172
|
+
const f = family === 'neutral' ? 'gray' : family;
|
|
173
|
+
|
|
174
|
+
for (const target of role.targets) {
|
|
175
|
+
const value = resolveTarget(f, intensity, target, role.roleId);
|
|
176
|
+
writes[`theme.${target.path}`] = value;
|
|
177
|
+
writes[`darkTheme.${target.path}`] = value;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// For primary-actions, also write the hover shade (one step darker than default)
|
|
181
|
+
if (role.roleId === 'role-primary-actions') {
|
|
182
|
+
const hoverValue = intensity === 'solid'
|
|
183
|
+
? `palette.colors.${f}.700`
|
|
184
|
+
: `palette.colors.${f}.200`;
|
|
185
|
+
writes['theme.semantic.colors.interactive.hover'] = hoverValue;
|
|
186
|
+
writes['darkTheme.semantic.colors.interactive.hover'] = hoverValue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return writes;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function resolveTarget(
|
|
193
|
+
family: string,
|
|
194
|
+
intensity: string,
|
|
195
|
+
target: RoleTarget,
|
|
196
|
+
roleId: string
|
|
197
|
+
): string {
|
|
198
|
+
const solid = intensity === 'solid';
|
|
199
|
+
|
|
200
|
+
// Header has nuanced per-sub-target shading
|
|
201
|
+
if (roleId === 'role-header') {
|
|
202
|
+
return resolveHeaderTarget(family, solid, target);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Input has special handling — foreground target is the placeholder
|
|
206
|
+
if (roleId === 'role-input') {
|
|
207
|
+
return resolveInputTarget(family, solid, target);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
switch (target.kind) {
|
|
211
|
+
case 'background':
|
|
212
|
+
return solid
|
|
213
|
+
? `palette.colors.${family}.500`
|
|
214
|
+
: `palette.colors.${family}.${family === 'gray' ? '50' : '100'}`;
|
|
215
|
+
case 'foreground':
|
|
216
|
+
return solid
|
|
217
|
+
? `palette.colors.${family === 'gray' ? 'gray' : family}.50`
|
|
218
|
+
: `palette.colors.${family === 'gray' ? 'gray' : family}.900`;
|
|
219
|
+
case 'border':
|
|
220
|
+
return solid
|
|
221
|
+
? `palette.colors.${family}.600`
|
|
222
|
+
: `palette.colors.${family}.200`;
|
|
223
|
+
case 'accent':
|
|
224
|
+
return solid
|
|
225
|
+
? `palette.colors.${family}.600`
|
|
226
|
+
: `palette.colors.${family}.400`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function resolveHeaderTarget(family: string, solid: boolean, target: RoleTarget): string {
|
|
231
|
+
const path = target.path;
|
|
232
|
+
|
|
233
|
+
if (path.endsWith('.background')) {
|
|
234
|
+
return solid
|
|
235
|
+
? `palette.colors.${family}.500`
|
|
236
|
+
: `palette.colors.${family}.${family === 'gray' ? '50' : '100'}`;
|
|
237
|
+
}
|
|
238
|
+
if (path.endsWith('.border')) {
|
|
239
|
+
return solid
|
|
240
|
+
? `palette.colors.${family}.600`
|
|
241
|
+
: `palette.colors.${family}.200`;
|
|
242
|
+
}
|
|
243
|
+
if (path.endsWith('.iconBackground')) {
|
|
244
|
+
return solid
|
|
245
|
+
? `palette.colors.${family}.${family === 'gray' ? '700' : '600'}`
|
|
246
|
+
: `palette.colors.${family}.500`;
|
|
247
|
+
}
|
|
248
|
+
if (path.endsWith('.iconForeground')) {
|
|
249
|
+
return solid
|
|
250
|
+
? `palette.colors.${family}.50`
|
|
251
|
+
: `palette.colors.${family}.50`;
|
|
252
|
+
}
|
|
253
|
+
if (path.endsWith('.titleForeground')) {
|
|
254
|
+
return solid
|
|
255
|
+
? `palette.colors.${family}.50`
|
|
256
|
+
: `palette.colors.${family}.${family === 'gray' ? '900' : '700'}`;
|
|
257
|
+
}
|
|
258
|
+
if (path.endsWith('.subtitleForeground')) {
|
|
259
|
+
return solid
|
|
260
|
+
? `palette.colors.${family}.200`
|
|
261
|
+
: `palette.colors.${family}.500`;
|
|
262
|
+
}
|
|
263
|
+
if (path.endsWith('.actionIconForeground')) {
|
|
264
|
+
return solid
|
|
265
|
+
? `palette.colors.${family}.200`
|
|
266
|
+
: `palette.colors.${family}.500`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Fallback
|
|
270
|
+
return `palette.colors.${family}.500`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolveInputTarget(family: string, solid: boolean, target: RoleTarget): string {
|
|
274
|
+
const path = target.path;
|
|
275
|
+
|
|
276
|
+
if (path.endsWith('.background')) {
|
|
277
|
+
return solid
|
|
278
|
+
? `palette.colors.${family}.${family === 'gray' ? '100' : '50'}`
|
|
279
|
+
: `palette.colors.${family}.${family === 'gray' ? '50' : '50'}`;
|
|
280
|
+
}
|
|
281
|
+
if (path.endsWith('.placeholder')) {
|
|
282
|
+
return `palette.colors.${family}.${solid ? '400' : '400'}`;
|
|
283
|
+
}
|
|
284
|
+
if (path.endsWith('.border') || path.endsWith('.ring')) {
|
|
285
|
+
return solid
|
|
286
|
+
? `palette.colors.${family}.500`
|
|
287
|
+
: `palette.colors.${family}.400`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return `palette.colors.${family}.500`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── Detection (reverse mapping) ────────────────────────────────
|
|
294
|
+
|
|
295
|
+
/** Result of detecting a role assignment from current state */
|
|
296
|
+
export interface DetectedRoleAssignment {
|
|
297
|
+
family: RoleFamily;
|
|
298
|
+
intensity: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Pattern: palette.colors.{family}.{shade} */
|
|
302
|
+
const PALETTE_REF_RE = /^palette\.colors\.(\w+)\.(\d+)$/;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Detect the current role assignment by reading token values and matching
|
|
306
|
+
* against known palette reference patterns.
|
|
307
|
+
*
|
|
308
|
+
* @param getValue - Function to read a theme token value (e.g., `(p) => state.get('theme.' + p)`)
|
|
309
|
+
* @param role - The role definition to detect against
|
|
310
|
+
* @returns Detected assignment or null if tokens don't match a known pattern
|
|
311
|
+
*/
|
|
312
|
+
export function detectRoleAssignment(
|
|
313
|
+
getValue: (path: string) => unknown,
|
|
314
|
+
role: RoleAssignmentOptions
|
|
315
|
+
): DetectedRoleAssignment | null {
|
|
316
|
+
// Read the first background target (or first target of any kind) to determine the family
|
|
317
|
+
const probeTarget = role.targets.find((t) => t.kind === 'background') ?? role.targets[0];
|
|
318
|
+
if (!probeTarget) return null;
|
|
319
|
+
|
|
320
|
+
const bgValue = String(getValue(probeTarget.path) ?? '');
|
|
321
|
+
const bgMatch = bgValue.match(PALETTE_REF_RE);
|
|
322
|
+
if (!bgMatch) return null;
|
|
323
|
+
|
|
324
|
+
const detectedFamily = bgMatch[1] as string;
|
|
325
|
+
|
|
326
|
+
// Normalize gray → gray (it's already the canonical name)
|
|
327
|
+
const family = ROLE_FAMILIES.includes(detectedFamily as RoleFamily)
|
|
328
|
+
? (detectedFamily as RoleFamily)
|
|
329
|
+
: null;
|
|
330
|
+
if (!family) return null;
|
|
331
|
+
|
|
332
|
+
// Try both intensities — shade-based guessing doesn't work for all target kinds
|
|
333
|
+
for (const intensity of ['solid', 'soft'] as const) {
|
|
334
|
+
const expected = resolveRoleAssignment(family, intensity, role);
|
|
335
|
+
const allMatch = role.targets.every((t) => {
|
|
336
|
+
const actual = String(getValue(t.path) ?? '');
|
|
337
|
+
return actual === expected[`theme.${t.path}`];
|
|
338
|
+
});
|
|
339
|
+
if (allMatch) return { family, intensity };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { COMPONENTS_SECTIONS, CONFIGURE_SECTIONS, INTERFACE_ROLES_SECTION } from "./sections";
|
|
4
|
+
import { ALL_ROLES } from "./role-mappings";
|
|
5
|
+
|
|
6
|
+
describe("theme editor scroll-to-bottom controls", () => {
|
|
7
|
+
it("exposes scroll-to-bottom config controls", () => {
|
|
8
|
+
const featureSection = CONFIGURE_SECTIONS.find((section) => section.id === "features");
|
|
9
|
+
|
|
10
|
+
expect(featureSection?.fields.some((field) => field.path === "features.scrollToBottom.enabled")).toBe(true);
|
|
11
|
+
expect(featureSection?.fields.some((field) => field.path === "features.scrollToBottom.iconName")).toBe(true);
|
|
12
|
+
expect(featureSection?.fields.some((field) => field.path === "features.scrollToBottom.label")).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("exposes scroll-to-bottom component token controls", () => {
|
|
16
|
+
const fieldPaths = COMPONENTS_SECTIONS.flatMap((section) => section.fields.map((field) => field.path));
|
|
17
|
+
|
|
18
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.background");
|
|
19
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.foreground");
|
|
20
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.border");
|
|
21
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.size");
|
|
22
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.borderRadius");
|
|
23
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.shadow");
|
|
24
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.padding");
|
|
25
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.gap");
|
|
26
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.fontSize");
|
|
27
|
+
expect(fieldPaths).toContain("theme.components.scrollToBottom.iconSize");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("adds a scroll-to-bottom interface role mapping", () => {
|
|
31
|
+
const role = ALL_ROLES.find((entry) => entry.roleId === "role-scroll-to-bottom");
|
|
32
|
+
|
|
33
|
+
expect(role).toBeDefined();
|
|
34
|
+
expect(role?.targets.map((target) => target.path)).toEqual(
|
|
35
|
+
expect.arrayContaining([
|
|
36
|
+
"components.scrollToBottom.background",
|
|
37
|
+
"components.scrollToBottom.foreground",
|
|
38
|
+
"components.scrollToBottom.border",
|
|
39
|
+
])
|
|
40
|
+
);
|
|
41
|
+
expect(INTERFACE_ROLES_SECTION.fields.some((field) => field.id === "role-scroll-to-bottom")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
});
|