@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
package/src/styles/widget.css
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
--persona-md-table-border-color: var(--persona-border, #e5e7eb);
|
|
14
14
|
--persona-md-table-header-bg: var(--persona-container, #f8fafc);
|
|
15
15
|
--persona-md-hr-color: var(--persona-divider, #e5e7eb);
|
|
16
|
-
--persona-md-blockquote-border-color: var(--persona-accent, #
|
|
16
|
+
--persona-md-blockquote-border-color: var(--persona-accent, #0f0f0f);
|
|
17
17
|
--persona-md-blockquote-text-color: var(--persona-muted, #6b7280);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -133,19 +133,19 @@
|
|
|
133
133
|
--persona-md-h6-line-height: 1.5;
|
|
134
134
|
|
|
135
135
|
/* Markdown Table Variables */
|
|
136
|
-
--persona-md-table-border-color: #e5e7eb;
|
|
137
|
-
--persona-md-table-header-bg: #f8fafc;
|
|
136
|
+
--persona-md-table-border-color: var(--persona-border, #e5e7eb);
|
|
137
|
+
--persona-md-table-header-bg: var(--persona-container, #f8fafc);
|
|
138
138
|
--persona-md-table-header-weight: 600;
|
|
139
139
|
--persona-md-table-cell-padding: 0.5rem 0.75rem;
|
|
140
140
|
--persona-md-table-border-radius: 0.375rem;
|
|
141
141
|
|
|
142
142
|
/* Markdown Horizontal Rule Variables */
|
|
143
|
-
--persona-md-hr-color: #e5e7eb;
|
|
143
|
+
--persona-md-hr-color: var(--persona-divider, #e5e7eb);
|
|
144
144
|
--persona-md-hr-height: 1px;
|
|
145
145
|
--persona-md-hr-margin: 1rem 0;
|
|
146
146
|
|
|
147
147
|
/* Markdown Blockquote Variables */
|
|
148
|
-
--persona-md-blockquote-border-color: #
|
|
148
|
+
--persona-md-blockquote-border-color: #171717;
|
|
149
149
|
--persona-md-blockquote-border-width: 3px;
|
|
150
150
|
--persona-md-blockquote-padding: 0.5rem 1rem;
|
|
151
151
|
--persona-md-blockquote-margin: 0.5rem 0;
|
|
@@ -154,8 +154,8 @@
|
|
|
154
154
|
--persona-md-blockquote-font-style: italic;
|
|
155
155
|
|
|
156
156
|
/* Markdown Code Block Variables */
|
|
157
|
-
--persona-md-code-block-bg: #f3f4f6;
|
|
158
|
-
--persona-md-code-block-border-color: #e5e7eb;
|
|
157
|
+
--persona-md-code-block-bg: var(--persona-container, #f3f4f6);
|
|
158
|
+
--persona-md-code-block-border-color: var(--persona-border, #e5e7eb);
|
|
159
159
|
--persona-md-code-block-text-color: inherit;
|
|
160
160
|
--persona-md-code-block-padding: 0.75rem;
|
|
161
161
|
--persona-md-code-block-border-radius: 0.375rem;
|
|
@@ -250,11 +250,11 @@
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
.persona-text-persona-primary {
|
|
253
|
-
color: var(--persona-primary, #
|
|
253
|
+
color: var(--persona-primary, #171717);
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
.persona-text-persona-call-to-action {
|
|
257
|
-
color: var(--persona-call-to-action, #ffffff);
|
|
257
|
+
color: var(--persona-call-to-action, var(--persona-text-inverse, #ffffff));
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
.persona-text-persona-muted {
|
|
@@ -266,11 +266,11 @@
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
.persona-bg-persona-accent {
|
|
269
|
-
background-color: var(--persona-accent, #
|
|
269
|
+
background-color: var(--persona-accent, #0f0f0f);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
.persona-bg-persona-primary {
|
|
273
|
-
background-color: var(--persona-primary, #111827);
|
|
273
|
+
background-color: var(--persona-button-primary-bg, var(--persona-primary, #111827));
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
.persona-italic {
|
|
@@ -533,8 +533,12 @@
|
|
|
533
533
|
border-color: #f1f5f9;
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
.persona-border-persona-secondary {
|
|
537
|
+
border-color: var(--persona-secondary, #7c3aed);
|
|
538
|
+
}
|
|
539
|
+
|
|
536
540
|
.persona-border-persona-border {
|
|
537
|
-
border-color: var(--persona-border, #
|
|
541
|
+
border-color: var(--persona-border, #e5e7eb);
|
|
538
542
|
}
|
|
539
543
|
|
|
540
544
|
.persona-border-t {
|
|
@@ -1209,7 +1213,7 @@
|
|
|
1209
1213
|
}
|
|
1210
1214
|
|
|
1211
1215
|
[data-persona-root] .persona-markdown-bubble a {
|
|
1212
|
-
color: var(--persona-md-link-color, var(--persona-accent, #
|
|
1216
|
+
color: var(--persona-md-link-color, var(--persona-accent, #0f0f0f));
|
|
1213
1217
|
text-decoration: underline;
|
|
1214
1218
|
}
|
|
1215
1219
|
|
|
@@ -1311,7 +1315,7 @@
|
|
|
1311
1315
|
}
|
|
1312
1316
|
|
|
1313
1317
|
[data-persona-root] .vanilla-message-assistant-bubble a {
|
|
1314
|
-
color: var(--persona-md-link-color, var(--persona-accent, #
|
|
1318
|
+
color: var(--persona-md-link-color, var(--persona-accent, #0f0f0f));
|
|
1315
1319
|
}
|
|
1316
1320
|
|
|
1317
1321
|
/* Markdown paragraph styles */
|
|
@@ -1791,7 +1795,7 @@
|
|
|
1791
1795
|
|
|
1792
1796
|
.persona-message-action-btn:focus {
|
|
1793
1797
|
outline: none;
|
|
1794
|
-
box-shadow: 0 0 0 2px var(--persona-accent, #
|
|
1798
|
+
box-shadow: 0 0 0 2px var(--persona-accent, #0a0a0a);
|
|
1795
1799
|
}
|
|
1796
1800
|
|
|
1797
1801
|
.persona-message-action-btn:focus:not(:focus-visible) {
|
|
@@ -1799,17 +1803,17 @@
|
|
|
1799
1803
|
}
|
|
1800
1804
|
|
|
1801
1805
|
.persona-message-action-btn:focus-visible {
|
|
1802
|
-
box-shadow: 0 0 0 2px var(--persona-accent, #
|
|
1806
|
+
box-shadow: 0 0 0 2px var(--persona-accent, #0a0a0a);
|
|
1803
1807
|
}
|
|
1804
1808
|
|
|
1805
1809
|
/* Active state (voted) */
|
|
1806
1810
|
.persona-message-action-btn.persona-message-action-active {
|
|
1807
|
-
background-color: var(--persona-accent, #
|
|
1811
|
+
background-color: var(--persona-accent, #0a0a0a);
|
|
1808
1812
|
color: #ffffff;
|
|
1809
1813
|
}
|
|
1810
1814
|
|
|
1811
1815
|
.persona-message-action-btn.persona-message-action-active:hover {
|
|
1812
|
-
background-color: var(--persona-accent, #
|
|
1816
|
+
background-color: var(--persona-accent, #0a0a0a);
|
|
1813
1817
|
color: #ffffff;
|
|
1814
1818
|
opacity: 0.9;
|
|
1815
1819
|
}
|
|
@@ -1951,7 +1955,7 @@
|
|
|
1951
1955
|
}
|
|
1952
1956
|
|
|
1953
1957
|
.persona-feedback-number-btn:hover {
|
|
1954
|
-
border-color: var(--persona-accent, #
|
|
1958
|
+
border-color: var(--persona-accent, #0a0a0a);
|
|
1955
1959
|
background: var(--persona-container, #f3f4f6);
|
|
1956
1960
|
}
|
|
1957
1961
|
|
|
@@ -1995,7 +1999,7 @@
|
|
|
1995
1999
|
|
|
1996
2000
|
.persona-feedback-comment:focus {
|
|
1997
2001
|
outline: none;
|
|
1998
|
-
border-color: var(--persona-accent, #
|
|
2002
|
+
border-color: var(--persona-accent, #0a0a0a);
|
|
1999
2003
|
box-shadow: 0 0 0 2px rgba(29, 78, 216, 0.15);
|
|
2000
2004
|
}
|
|
2001
2005
|
|
|
@@ -2031,8 +2035,8 @@
|
|
|
2031
2035
|
}
|
|
2032
2036
|
|
|
2033
2037
|
.persona-feedback-btn-submit {
|
|
2034
|
-
background: var(--persona-accent, #
|
|
2035
|
-
border: 1px solid var(--persona-accent, #
|
|
2038
|
+
background: var(--persona-accent, #0a0a0a);
|
|
2039
|
+
border: 1px solid var(--persona-accent, #0a0a0a);
|
|
2036
2040
|
color: #ffffff;
|
|
2037
2041
|
}
|
|
2038
2042
|
|
|
@@ -2247,7 +2251,7 @@
|
|
|
2247
2251
|
}
|
|
2248
2252
|
|
|
2249
2253
|
[data-persona-root] .persona-icon-btn:focus-visible {
|
|
2250
|
-
outline: 2px solid var(--persona-accent, #
|
|
2254
|
+
outline: 2px solid var(--persona-accent, #171717);
|
|
2251
2255
|
outline-offset: 2px;
|
|
2252
2256
|
}
|
|
2253
2257
|
|
|
@@ -2279,7 +2283,7 @@
|
|
|
2279
2283
|
}
|
|
2280
2284
|
|
|
2281
2285
|
[data-persona-root] .persona-label-btn:focus-visible {
|
|
2282
|
-
outline: 2px solid var(--persona-accent, #
|
|
2286
|
+
outline: 2px solid var(--persona-accent, #171717);
|
|
2283
2287
|
outline-offset: 2px;
|
|
2284
2288
|
}
|
|
2285
2289
|
|
|
@@ -2294,7 +2298,7 @@
|
|
|
2294
2298
|
}
|
|
2295
2299
|
|
|
2296
2300
|
[data-persona-root] .persona-label-btn--primary {
|
|
2297
|
-
background: var(--persona-primary, #
|
|
2301
|
+
background: var(--persona-primary, #171717);
|
|
2298
2302
|
color: var(--persona-text-inverse, #ffffff);
|
|
2299
2303
|
border-color: transparent;
|
|
2300
2304
|
}
|
|
@@ -2316,6 +2320,46 @@
|
|
|
2316
2320
|
background: var(--persona-label-btn-hover-bg, var(--persona-container, #f3f4f6));
|
|
2317
2321
|
}
|
|
2318
2322
|
|
|
2323
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator {
|
|
2324
|
+
display: inline-flex;
|
|
2325
|
+
align-items: center;
|
|
2326
|
+
justify-content: center;
|
|
2327
|
+
gap: var(--persona-scroll-to-bottom-gap, 0.5rem);
|
|
2328
|
+
min-height: var(--persona-scroll-to-bottom-size, 40px);
|
|
2329
|
+
border-radius: var(--persona-scroll-to-bottom-radius, var(--persona-radius-full, 9999px));
|
|
2330
|
+
border: 1px solid var(--persona-scroll-to-bottom-border, var(--persona-primary, #111827));
|
|
2331
|
+
background: var(--persona-scroll-to-bottom-bg, var(--persona-button-primary-bg, var(--persona-accent, #0f0f0f)));
|
|
2332
|
+
color: var(--persona-scroll-to-bottom-fg, var(--persona-button-primary-fg, var(--persona-text-inverse, #ffffff)));
|
|
2333
|
+
box-shadow: var(--persona-scroll-to-bottom-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
|
|
2334
|
+
font-size: var(--persona-scroll-to-bottom-font-size, 0.875rem);
|
|
2335
|
+
line-height: 1;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator[data-persona-scroll-to-bottom-has-label="true"] {
|
|
2339
|
+
padding: var(--persona-scroll-to-bottom-padding, 0.5rem 0.875rem);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator[data-persona-scroll-to-bottom-has-label="false"] {
|
|
2343
|
+
width: var(--persona-scroll-to-bottom-size, 40px);
|
|
2344
|
+
height: var(--persona-scroll-to-bottom-size, 40px);
|
|
2345
|
+
padding: 0;
|
|
2346
|
+
gap: 0;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator svg {
|
|
2350
|
+
width: var(--persona-scroll-to-bottom-icon-size, 14px);
|
|
2351
|
+
height: var(--persona-scroll-to-bottom-icon-size, 14px);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator:hover {
|
|
2355
|
+
opacity: 0.92;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
[data-persona-root] .persona-scroll-to-bottom-indicator:focus-visible {
|
|
2359
|
+
outline: 2px solid var(--persona-accent, #171717);
|
|
2360
|
+
outline-offset: 2px;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2319
2363
|
/* Toggle group — mutually exclusive button set created by createToggleGroup() */
|
|
2320
2364
|
[data-persona-root] .persona-toggle-group {
|
|
2321
2365
|
display: inline-flex;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/** Color parsing, normalization, and scale generation utilities (pure functions — no DOM) */
|
|
2
|
+
|
|
3
|
+
import type { ColorShade } from '../types/theme';
|
|
4
|
+
|
|
5
|
+
// ─── CSS Value Parsing ──────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
interface ParsedCssValue {
|
|
8
|
+
value: number;
|
|
9
|
+
unit: 'px' | 'rem';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseCssValue(cssValue: string): ParsedCssValue {
|
|
13
|
+
const trimmed = cssValue.trim();
|
|
14
|
+
|
|
15
|
+
if (trimmed === '9999px') {
|
|
16
|
+
return { value: 100, unit: 'px' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const match = trimmed.match(/^([\d.]+)(px|rem)$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
const numValue = parseFloat(trimmed);
|
|
22
|
+
return { value: isNaN(numValue) ? 0 : numValue, unit: 'px' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { value: parseFloat(match[1]), unit: match[2] as 'px' | 'rem' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatCssValue(value: number, unit: 'px' | 'rem'): string {
|
|
29
|
+
if (unit === 'rem') {
|
|
30
|
+
return `${value}rem`;
|
|
31
|
+
}
|
|
32
|
+
return `${value}px`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function convertToPx(value: number, unit: 'px' | 'rem'): number {
|
|
36
|
+
return unit === 'rem' ? value * 16 : value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function convertFromPx(pxValue: number, unit: 'px' | 'rem'): number {
|
|
40
|
+
return unit === 'rem' ? pxValue / 16 : pxValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Color Normalization ────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export function normalizeColorValue(value: string): string {
|
|
46
|
+
if (!value) return '#000000';
|
|
47
|
+
|
|
48
|
+
const trimmed = value.trim().toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (trimmed === 'transparent') return 'transparent';
|
|
51
|
+
if (trimmed.startsWith('rgba') || trimmed.startsWith('rgb')) return trimmed;
|
|
52
|
+
|
|
53
|
+
if (trimmed.startsWith('#')) {
|
|
54
|
+
if (trimmed.length === 4) {
|
|
55
|
+
return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
|
|
56
|
+
}
|
|
57
|
+
return trimmed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `#${trimmed}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isValidHex(value: string): boolean {
|
|
64
|
+
return /^#[0-9A-Fa-f]{6}$/.test(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── WCAG Contrast ──────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compute WCAG 2.x contrast ratio between two hex colors.
|
|
71
|
+
* Returns a number ≥ 1 (e.g. 4.5 for AA normal text).
|
|
72
|
+
*/
|
|
73
|
+
export function wcagContrastRatio(hex1: string, hex2: string): number {
|
|
74
|
+
const luminance = (hex: string): number => {
|
|
75
|
+
const norm = normalizeColorValue(hex);
|
|
76
|
+
const channels = [1, 3, 5].map((i) => {
|
|
77
|
+
const v = parseInt(norm.slice(i, i + 2), 16) / 255;
|
|
78
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
79
|
+
});
|
|
80
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
81
|
+
};
|
|
82
|
+
const l1 = luminance(hex1);
|
|
83
|
+
const l2 = luminance(hex2);
|
|
84
|
+
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── HSL Conversion ─────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
export function hexToHsl(hex: string): { h: number; s: number; l: number } {
|
|
90
|
+
const normalized = normalizeColorValue(hex);
|
|
91
|
+
const r = parseInt(normalized.slice(1, 3), 16) / 255;
|
|
92
|
+
const g = parseInt(normalized.slice(3, 5), 16) / 255;
|
|
93
|
+
const b = parseInt(normalized.slice(5, 7), 16) / 255;
|
|
94
|
+
|
|
95
|
+
const max = Math.max(r, g, b);
|
|
96
|
+
const min = Math.min(r, g, b);
|
|
97
|
+
const l = (max + min) / 2;
|
|
98
|
+
|
|
99
|
+
if (max === min) {
|
|
100
|
+
return { h: 0, s: 0, l };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const d = max - min;
|
|
104
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
105
|
+
|
|
106
|
+
let h: number;
|
|
107
|
+
switch (max) {
|
|
108
|
+
case r:
|
|
109
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
110
|
+
break;
|
|
111
|
+
case g:
|
|
112
|
+
h = ((b - r) / d + 2) / 6;
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
h = ((r - g) / d + 4) / 6;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { h: h * 360, s, l };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function hslToHex(h: number, s: number, l: number): string {
|
|
123
|
+
const hue = ((h % 360) + 360) % 360;
|
|
124
|
+
|
|
125
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
126
|
+
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
127
|
+
const m = l - c / 2;
|
|
128
|
+
|
|
129
|
+
let r: number, g: number, b: number;
|
|
130
|
+
|
|
131
|
+
if (hue < 60) {
|
|
132
|
+
[r, g, b] = [c, x, 0];
|
|
133
|
+
} else if (hue < 120) {
|
|
134
|
+
[r, g, b] = [x, c, 0];
|
|
135
|
+
} else if (hue < 180) {
|
|
136
|
+
[r, g, b] = [0, c, x];
|
|
137
|
+
} else if (hue < 240) {
|
|
138
|
+
[r, g, b] = [0, x, c];
|
|
139
|
+
} else if (hue < 300) {
|
|
140
|
+
[r, g, b] = [x, 0, c];
|
|
141
|
+
} else {
|
|
142
|
+
[r, g, b] = [c, 0, x];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const toHex = (n: number) => {
|
|
146
|
+
const hex = Math.round((n + m) * 255).toString(16);
|
|
147
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Color Scale Generation ─────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate a full color scale (50-950) from a base color (shade 500).
|
|
157
|
+
* Uses HSL-based lightness interpolation for natural-looking scales.
|
|
158
|
+
*/
|
|
159
|
+
export function generateColorScale(baseHex: string): ColorShade {
|
|
160
|
+
const { h, s, l } = hexToHsl(baseHex);
|
|
161
|
+
|
|
162
|
+
const lightnessByShade: Record<string, number> = {
|
|
163
|
+
'50': 0.97,
|
|
164
|
+
'100': 0.94,
|
|
165
|
+
'200': 0.87,
|
|
166
|
+
'300': 0.77,
|
|
167
|
+
'400': 0.64,
|
|
168
|
+
'500': l,
|
|
169
|
+
'600': Math.max(0.05, l - 0.1),
|
|
170
|
+
'700': Math.max(0.05, l - 0.2),
|
|
171
|
+
'800': Math.max(0.05, l - 0.28),
|
|
172
|
+
'900': Math.max(0.05, l - 0.35),
|
|
173
|
+
'950': Math.max(0.03, l - 0.42),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const saturationByShade: Record<string, number> = {
|
|
177
|
+
'50': Math.min(1, s * 0.85),
|
|
178
|
+
'100': Math.min(1, s * 0.9),
|
|
179
|
+
'200': Math.min(1, s * 0.95),
|
|
180
|
+
'300': s,
|
|
181
|
+
'400': s,
|
|
182
|
+
'500': s,
|
|
183
|
+
'600': Math.min(1, s * 1.05),
|
|
184
|
+
'700': Math.min(1, s * 1.05),
|
|
185
|
+
'800': Math.min(1, s * 1.0),
|
|
186
|
+
'900': Math.min(1, s * 0.95),
|
|
187
|
+
'950': Math.min(1, s * 0.9),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const scale: ColorShade = {};
|
|
191
|
+
for (const [shade, targetL] of Object.entries(lightnessByShade)) {
|
|
192
|
+
const targetS = saturationByShade[shade];
|
|
193
|
+
scale[shade] = hslToHex(h, targetS, targetL);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return scale;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── Constants & Helpers ────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
export const SHADE_KEYS = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] as const;
|
|
202
|
+
|
|
203
|
+
export const COLOR_FAMILIES = ['primary', 'secondary', 'accent', 'gray', 'success', 'warning', 'error', 'info'] as const;
|
|
204
|
+
|
|
205
|
+
export function paletteColorPath(family: string, shade: string): string {
|
|
206
|
+
return `palette.colors.${family}.${shade}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resolve a theme dot-path to a concrete CSS color string.
|
|
211
|
+
* Follows token references recursively (palette.*, semantic.*, components.*).
|
|
212
|
+
*/
|
|
213
|
+
export function resolveThemeColorPath(
|
|
214
|
+
get: (path: string) => unknown,
|
|
215
|
+
path: string,
|
|
216
|
+
depth = 0
|
|
217
|
+
): string {
|
|
218
|
+
if (depth > 5) return '#cbd5e1';
|
|
219
|
+
|
|
220
|
+
const raw = get(path);
|
|
221
|
+
if (typeof raw !== 'string') return '#cbd5e1';
|
|
222
|
+
if (raw.startsWith('#') || raw.startsWith('rgb') || raw === 'transparent') return raw;
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
raw.startsWith('palette.') ||
|
|
226
|
+
raw.startsWith('semantic.') ||
|
|
227
|
+
raw.startsWith('components.')
|
|
228
|
+
) {
|
|
229
|
+
return resolveThemeColorPath(get, `theme.${raw}`, depth + 1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return '#cbd5e1';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function tokenRefDisplayName(path: string): string {
|
|
236
|
+
if (!path.startsWith('palette.') && !path.startsWith('semantic.')) {
|
|
237
|
+
return path;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const parts = path.split('.');
|
|
241
|
+
if (parts[0] === 'palette' && parts[1] === 'colors') {
|
|
242
|
+
const family = parts[2];
|
|
243
|
+
const shade = parts[3];
|
|
244
|
+
return `${family.charAt(0).toUpperCase() + family.slice(1)} ${shade}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (parts[0] === 'semantic') {
|
|
248
|
+
return parts.slice(1).map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' ');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return parts[parts.length - 1];
|
|
252
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** @runtypelabs/persona/theme-editor — Headless theme editor core */
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
export type {
|
|
5
|
+
FieldType,
|
|
6
|
+
FieldDef,
|
|
7
|
+
SectionDef,
|
|
8
|
+
SectionPreset,
|
|
9
|
+
TabDef,
|
|
10
|
+
SubGroupDef,
|
|
11
|
+
SliderOptions,
|
|
12
|
+
SelectOption,
|
|
13
|
+
ColorScaleOptions,
|
|
14
|
+
TokenRefOptions,
|
|
15
|
+
RoleTargetKind,
|
|
16
|
+
RoleTarget,
|
|
17
|
+
RoleIntensity,
|
|
18
|
+
RoleAssignmentOptions,
|
|
19
|
+
ThemeEditorPreset,
|
|
20
|
+
ConfiguratorSnapshot,
|
|
21
|
+
ConfigChangeListener,
|
|
22
|
+
OnChangeCallback,
|
|
23
|
+
} from './types';
|
|
24
|
+
|
|
25
|
+
// State
|
|
26
|
+
export { ThemeEditorState } from './state';
|
|
27
|
+
|
|
28
|
+
// Sections
|
|
29
|
+
export {
|
|
30
|
+
STYLE_SECTIONS,
|
|
31
|
+
STYLE_SECTIONS_V2,
|
|
32
|
+
THEME_SECTION,
|
|
33
|
+
BRAND_PALETTE_SECTION,
|
|
34
|
+
STATUS_PALETTE_SECTION,
|
|
35
|
+
INTERFACE_ROLES_SECTION,
|
|
36
|
+
STATUS_COLORS_SECTION,
|
|
37
|
+
ADVANCED_TOKENS_SECTION,
|
|
38
|
+
COLORS_SECTIONS,
|
|
39
|
+
PALETTE_SECTION,
|
|
40
|
+
SEMANTIC_COLORS_SECTION,
|
|
41
|
+
COMPONENTS_SECTIONS,
|
|
42
|
+
COMPONENT_SHAPE_SECTIONS,
|
|
43
|
+
COMPONENT_COLOR_SECTIONS,
|
|
44
|
+
CONFIGURE_SECTIONS,
|
|
45
|
+
CONFIGURE_SUB_GROUPS,
|
|
46
|
+
ALL_TABS,
|
|
47
|
+
scopeSection,
|
|
48
|
+
findSection,
|
|
49
|
+
} from './sections';
|
|
50
|
+
|
|
51
|
+
// Presets
|
|
52
|
+
export {
|
|
53
|
+
BUILT_IN_PRESETS,
|
|
54
|
+
THEME_EDITOR_PRESETS,
|
|
55
|
+
getThemeEditorPreset,
|
|
56
|
+
} from './presets';
|
|
57
|
+
|
|
58
|
+
// Preview renderer
|
|
59
|
+
export { createThemePreview } from './preview';
|
|
60
|
+
export type {
|
|
61
|
+
ThemePreviewOptions,
|
|
62
|
+
ThemePreviewHandle,
|
|
63
|
+
PreviewLifecycleContext,
|
|
64
|
+
PreviewDevice,
|
|
65
|
+
PreviewShellMode,
|
|
66
|
+
CompareMode,
|
|
67
|
+
} from './preview';
|
|
68
|
+
|
|
69
|
+
// Preview building blocks (for advanced/custom preview renderers)
|
|
70
|
+
export {
|
|
71
|
+
DEVICE_DIMENSIONS,
|
|
72
|
+
ZOOM_MIN,
|
|
73
|
+
ZOOM_MAX,
|
|
74
|
+
SHELL_STYLE_ID,
|
|
75
|
+
PREVIEW_STORAGE_ADAPTER,
|
|
76
|
+
HOME_SUGGESTION_CHIPS,
|
|
77
|
+
MOCK_BROWSER_CONTENT,
|
|
78
|
+
MOCK_WORKSPACE_CONTENT,
|
|
79
|
+
escapeHtml,
|
|
80
|
+
getShellPalette,
|
|
81
|
+
buildShellCss,
|
|
82
|
+
applyShellTheme,
|
|
83
|
+
buildSrcdoc,
|
|
84
|
+
createPreviewMessages,
|
|
85
|
+
applySceneConfig,
|
|
86
|
+
buildPreviewConfig,
|
|
87
|
+
} from './preview-utils';
|
|
88
|
+
export type {
|
|
89
|
+
PreviewScene,
|
|
90
|
+
PreviewShellPalette,
|
|
91
|
+
PreviewConfigOptions,
|
|
92
|
+
} from './preview-utils';
|
|
93
|
+
|
|
94
|
+
// Role mappings (Interface Roles editor)
|
|
95
|
+
export {
|
|
96
|
+
ROLE_INTENSITIES,
|
|
97
|
+
ROLE_FAMILIES,
|
|
98
|
+
ROLE_FAMILY_LABELS,
|
|
99
|
+
ROLE_SURFACES,
|
|
100
|
+
ROLE_HEADER,
|
|
101
|
+
ROLE_USER_MESSAGES,
|
|
102
|
+
ROLE_ASSISTANT_MESSAGES,
|
|
103
|
+
ROLE_PRIMARY_ACTIONS,
|
|
104
|
+
ROLE_SCROLL_TO_BOTTOM,
|
|
105
|
+
ROLE_INPUT,
|
|
106
|
+
ROLE_LINKS_FOCUS,
|
|
107
|
+
ROLE_BORDERS,
|
|
108
|
+
ALL_ROLES,
|
|
109
|
+
resolveRoleAssignment,
|
|
110
|
+
detectRoleAssignment,
|
|
111
|
+
} from './role-mappings';
|
|
112
|
+
export type { RoleFamily, DetectedRoleAssignment } from './role-mappings';
|
|
113
|
+
|
|
114
|
+
// Color utilities
|
|
115
|
+
export {
|
|
116
|
+
parseCssValue,
|
|
117
|
+
formatCssValue,
|
|
118
|
+
convertToPx,
|
|
119
|
+
convertFromPx,
|
|
120
|
+
normalizeColorValue,
|
|
121
|
+
isValidHex,
|
|
122
|
+
wcagContrastRatio,
|
|
123
|
+
hexToHsl,
|
|
124
|
+
hslToHex,
|
|
125
|
+
generateColorScale,
|
|
126
|
+
SHADE_KEYS,
|
|
127
|
+
COLOR_FAMILIES,
|
|
128
|
+
paletteColorPath,
|
|
129
|
+
resolveThemeColorPath,
|
|
130
|
+
tokenRefDisplayName,
|
|
131
|
+
} from './color-utils';
|