@runtypelabs/persona 3.5.1 → 3.6.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.
Files changed (45) hide show
  1. package/dist/index.cjs +30 -30
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +14 -0
  4. package/dist/index.d.ts +14 -0
  5. package/dist/index.global.js +41 -41
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +29 -29
  8. package/dist/index.js.map +1 -1
  9. package/dist/theme-editor.cjs +17728 -0
  10. package/dist/theme-editor.d.cts +3857 -0
  11. package/dist/theme-editor.d.ts +3857 -0
  12. package/dist/theme-editor.js +17623 -0
  13. package/dist/theme-reference.cjs +1 -1
  14. package/dist/theme-reference.d.cts +14 -0
  15. package/dist/theme-reference.d.ts +14 -0
  16. package/dist/theme-reference.js +1 -1
  17. package/dist/widget.css +29 -25
  18. package/package.json +9 -7
  19. package/src/components/artifact-card.ts +1 -1
  20. package/src/components/composer-builder.ts +16 -29
  21. package/src/components/demo-carousel.ts +4 -4
  22. package/src/components/event-stream-view.ts +1 -1
  23. package/src/components/header-builder.ts +2 -2
  24. package/src/components/launcher.ts +9 -0
  25. package/src/components/message-bubble.ts +9 -3
  26. package/src/components/suggestions.ts +1 -1
  27. package/src/defaults.ts +9 -9
  28. package/src/styles/widget.css +29 -25
  29. package/src/theme-editor/color-utils.ts +252 -0
  30. package/src/theme-editor/index.ts +130 -0
  31. package/src/theme-editor/presets.ts +144 -0
  32. package/src/theme-editor/preview-utils.ts +265 -0
  33. package/src/theme-editor/preview.ts +445 -0
  34. package/src/theme-editor/role-mappings.ts +331 -0
  35. package/src/theme-editor/sections.ts +952 -0
  36. package/src/theme-editor/state.ts +298 -0
  37. package/src/theme-editor/types.ts +177 -0
  38. package/src/theme-editor.ts +2 -0
  39. package/src/types/theme.ts +1 -0
  40. package/src/ui.ts +53 -58
  41. package/src/utils/plugins.ts +1 -1
  42. package/src/utils/theme.test.ts +10 -8
  43. package/src/utils/theme.ts +11 -11
  44. package/src/utils/tokens.ts +88 -41
  45. package/widget.css +0 -1
@@ -140,15 +140,21 @@ export const createTypingIndicator = (): HTMLElement => {
140
140
  container.className = "persona-flex persona-items-center persona-space-x-1 persona-h-5 persona-mt-2";
141
141
 
142
142
  const dot1 = document.createElement("div");
143
- dot1.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
143
+ dot1.className = "persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
144
+ dot1.style.backgroundColor = "currentColor";
145
+ dot1.style.opacity = "0.4";
144
146
  dot1.style.animationDelay = "0ms";
145
147
 
146
148
  const dot2 = document.createElement("div");
147
- dot2.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
149
+ dot2.className = "persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
150
+ dot2.style.backgroundColor = "currentColor";
151
+ dot2.style.opacity = "0.4";
148
152
  dot2.style.animationDelay = "250ms";
149
153
 
150
154
  const dot3 = document.createElement("div");
151
- dot3.className = "persona-bg-persona-primary persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
155
+ dot3.className = "persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5";
156
+ dot3.style.backgroundColor = "currentColor";
157
+ dot3.style.opacity = "0.4";
152
158
  dot3.style.animationDelay = "500ms";
153
159
 
154
160
  const srOnly = document.createElement("span");
@@ -52,7 +52,7 @@ export const createSuggestions = (container: HTMLElement): SuggestionButtons =>
52
52
  chips.forEach((chip) => {
53
53
  const btn = createElement(
54
54
  "button",
55
- "persona-rounded-button persona-bg-persona-surface persona-px-3 persona-py-1.5 persona-text-xs persona-font-medium persona-text-persona-muted hover:persona-opacity-90 persona-cursor-pointer persona-border persona-border-gray-200"
55
+ "persona-rounded-button persona-bg-persona-surface persona-px-3 persona-py-1.5 persona-text-xs persona-font-medium persona-text-persona-primary hover:persona-opacity-80 persona-cursor-pointer persona-border persona-border-persona-border"
56
56
  ) as HTMLButtonElement;
57
57
  btn.type = "button";
58
58
  btn.textContent = chip;
package/src/defaults.ts CHANGED
@@ -23,6 +23,8 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
23
23
  title: "Chat Assistant",
24
24
  subtitle: "Here to help you get answers fast",
25
25
  agentIconText: "💬",
26
+ agentIconName: "bot",
27
+ headerIconName: "bot",
26
28
  position: "bottom-right",
27
29
  width: "min(400px, calc(100vw - 24px))",
28
30
  heightOffset: 0,
@@ -35,8 +37,8 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
35
37
  callToActionIconText: "",
36
38
  callToActionIconSize: "32px",
37
39
  callToActionIconPadding: "5px",
38
- callToActionIconColor: "#000000",
39
- callToActionIconBackgroundColor: "#ffffff",
40
+ callToActionIconColor: undefined,
41
+ callToActionIconBackgroundColor: undefined,
40
42
  // closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
41
43
  closeButtonBackgroundColor: "transparent",
42
44
  clearChat: {
@@ -52,7 +54,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
52
54
  paddingY: "0px",
53
55
  },
54
56
  headerIconHidden: false,
55
- border: "1px solid #e5e7eb",
57
+ border: undefined,
56
58
  shadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
57
59
  },
58
60
  copy: {
@@ -65,9 +67,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
65
67
  borderWidth: "0px",
66
68
  paddingX: "12px",
67
69
  paddingY: "10px",
68
- backgroundColor: "#111827",
69
- textColor: "#ffffff",
70
- borderColor: "#60a5fa",
70
+ borderColor: undefined,
71
71
  useIcon: true,
72
72
  iconText: "↑",
73
73
  size: "40px",
@@ -90,11 +90,11 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
90
90
  borderWidth: "0px",
91
91
  paddingX: "9px",
92
92
  paddingY: "14px",
93
- iconColor: "#111827",
93
+ iconColor: undefined,
94
94
  backgroundColor: "transparent",
95
95
  borderColor: "transparent",
96
- recordingIconColor: "#ffffff",
97
- recordingBackgroundColor: "#ef4444",
96
+ recordingIconColor: undefined,
97
+ recordingBackgroundColor: undefined,
98
98
  recordingBorderColor: "transparent",
99
99
  showTooltip: true,
100
100
  tooltipText: "Start voice recognition",
@@ -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, #3b82f6);
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: #3b82f6;
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, #111827);
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, #2563eb);
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, #f1f5f9);
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, #3b82f6));
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, #3b82f6));
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, #1d4ed8);
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, #1d4ed8);
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, #1d4ed8);
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, #1d4ed8);
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, #1d4ed8);
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, #1d4ed8);
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, #1d4ed8);
2035
- border: 1px solid var(--persona-accent, #1d4ed8);
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, #3b82f6);
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, #3b82f6);
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, #3b82f6);
2301
+ background: var(--persona-primary, #171717);
2298
2302
  color: var(--persona-text-inverse, #ffffff);
2299
2303
  border-color: transparent;
2300
2304
  }
@@ -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,130 @@
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_INPUT,
105
+ ROLE_LINKS_FOCUS,
106
+ ROLE_BORDERS,
107
+ ALL_ROLES,
108
+ resolveRoleAssignment,
109
+ detectRoleAssignment,
110
+ } from './role-mappings';
111
+ export type { RoleFamily, DetectedRoleAssignment } from './role-mappings';
112
+
113
+ // Color utilities
114
+ export {
115
+ parseCssValue,
116
+ formatCssValue,
117
+ convertToPx,
118
+ convertFromPx,
119
+ normalizeColorValue,
120
+ isValidHex,
121
+ wcagContrastRatio,
122
+ hexToHsl,
123
+ hslToHex,
124
+ generateColorScale,
125
+ SHADE_KEYS,
126
+ COLOR_FAMILIES,
127
+ paletteColorPath,
128
+ resolveThemeColorPath,
129
+ tokenRefDisplayName,
130
+ } from './color-utils';