@optilogic/core 1.0.0-beta.9 → 1.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/dist/index.cjs +1115 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +326 -1
- package/dist/index.d.ts +326 -1
- package/dist/index.js +1097 -46
- package/dist/index.js.map +1 -1
- package/dist/styles.css +22 -0
- package/dist/tailwind-preset.cjs +17 -2
- package/dist/tailwind-preset.cjs.map +1 -1
- package/dist/tailwind-preset.js +17 -2
- package/dist/tailwind-preset.js.map +1 -1
- package/package.json +15 -1
- package/src/components/autocomplete.tsx +2 -1
- package/src/components/button.tsx +10 -8
- package/src/components/calendar.tsx +7 -7
- package/src/components/data-grid/DataGrid.tsx +6 -1
- package/src/components/data-grid/components/CellEditor.tsx +3 -3
- package/src/components/data-grid/hooks/useDataGridState.ts +18 -3
- package/src/components/data-grid/types.ts +4 -0
- package/src/components/data-grid/utils/dataProcessing.ts +40 -11
- package/src/components/date-picker.tsx +2 -1
- package/src/components/dropdown-menu.tsx +1 -1
- package/src/components/file-view/FileView.tsx +147 -0
- package/src/components/file-view/components/CodeRenderer.tsx +97 -0
- package/src/components/file-view/components/CsvRenderer.tsx +127 -0
- package/src/components/file-view/components/HtmlRenderer.tsx +24 -0
- package/src/components/file-view/components/ImageRenderer.tsx +67 -0
- package/src/components/file-view/components/MarkdownRenderer.tsx +304 -0
- package/src/components/file-view/components/PlainTextRenderer.tsx +27 -0
- package/src/components/file-view/components/index.ts +4 -0
- package/src/components/file-view/hooks/index.ts +5 -0
- package/src/components/file-view/hooks/useContentType.ts +34 -0
- package/src/components/file-view/hooks/useDarkMode.ts +62 -0
- package/src/components/file-view/hooks/useHighlightedTokens.ts +83 -0
- package/src/components/file-view/hooks/useShikiHighlighter.ts +69 -0
- package/src/components/file-view/index.ts +47 -0
- package/src/components/file-view/types.ts +180 -0
- package/src/components/file-view/utils/contentTypeDetection.ts +157 -0
- package/src/components/file-view/utils/index.ts +12 -0
- package/src/components/file-view/utils/languageMapping.ts +78 -0
- package/src/components/file-view/utils/rendererRegistry.ts +42 -0
- package/src/components/input.tsx +1 -1
- package/src/components/popover.tsx +1 -1
- package/src/components/select.tsx +1 -1
- package/src/components/switch.tsx +5 -3
- package/src/components/textarea.tsx +1 -1
- package/src/index.ts +39 -0
- package/src/styles.css +22 -0
- package/src/tailwind-preset.ts +17 -1
- package/src/theme/index.ts +5 -0
- package/src/theme/presets.ts +112 -2
- package/src/theme/types.ts +35 -0
- package/src/theme/utils.ts +231 -0
package/src/theme/presets.ts
CHANGED
|
@@ -40,11 +40,21 @@ export const GREEN_THEME: Theme = {
|
|
|
40
40
|
border: "#243630",
|
|
41
41
|
input: "#243630",
|
|
42
42
|
ring: "#6FCF97",
|
|
43
|
+
toggleTrack: "#354840",
|
|
44
|
+
toggleTrackForeground: "#E6F5EC",
|
|
45
|
+
inputHover: "#6FCF97",
|
|
43
46
|
chart1: "#6FCF97",
|
|
44
47
|
chart2: "#8FE3B0",
|
|
45
48
|
chart3: "#9DB8A8",
|
|
46
49
|
chart4: "#2d4038",
|
|
47
50
|
chart5: "#354840",
|
|
51
|
+
chart6: "#4CAF50",
|
|
52
|
+
chart7: "#81C784",
|
|
53
|
+
chart8: "#F2C94C",
|
|
54
|
+
chart9: "#EB5757",
|
|
55
|
+
chart10: "#56CCF2",
|
|
56
|
+
chart11: "#BB6BD9",
|
|
57
|
+
chart12: "#2D9CDB",
|
|
48
58
|
radius: "0.5rem",
|
|
49
59
|
};
|
|
50
60
|
|
|
@@ -79,13 +89,23 @@ export const OPTILOGIC_LEGACY_THEME: Theme = {
|
|
|
79
89
|
chip: "#B8C5F9",
|
|
80
90
|
chipForeground: "#0C0A5A",
|
|
81
91
|
border: "#D0D0D0",
|
|
82
|
-
input: "#
|
|
92
|
+
input: "#D0D0D0",
|
|
83
93
|
ring: "#5766F2",
|
|
94
|
+
toggleTrack: "#D0D0D0",
|
|
95
|
+
toggleTrackForeground: "#FFFFFF",
|
|
96
|
+
inputHover: "#5766F2",
|
|
84
97
|
chart1: "#78D237",
|
|
85
98
|
chart2: "#F5CF47",
|
|
86
99
|
chart3: "#5766F2",
|
|
87
100
|
chart4: "#44BD7E",
|
|
88
101
|
chart5: "#929BEF",
|
|
102
|
+
chart6: "#DB2828",
|
|
103
|
+
chart7: "#E07B39",
|
|
104
|
+
chart8: "#2BBBAD",
|
|
105
|
+
chart9: "#A5673F",
|
|
106
|
+
chart10: "#00B5AD",
|
|
107
|
+
chart11: "#6435C9",
|
|
108
|
+
chart12: "#E03997",
|
|
89
109
|
radius: "0.5rem",
|
|
90
110
|
};
|
|
91
111
|
|
|
@@ -122,11 +142,21 @@ export const FUTURISTIC_THEME: Theme = {
|
|
|
122
142
|
border: "#1e293b",
|
|
123
143
|
input: "#1e293b",
|
|
124
144
|
ring: "#6366f1",
|
|
145
|
+
toggleTrack: "#334155",
|
|
146
|
+
toggleTrackForeground: "#e0e7ff",
|
|
147
|
+
inputHover: "#6366f1",
|
|
125
148
|
chart1: "#6366f1",
|
|
126
149
|
chart2: "#8b5cf6",
|
|
127
150
|
chart3: "#a855f7",
|
|
128
151
|
chart4: "#ec4899",
|
|
129
152
|
chart5: "#f43f5e",
|
|
153
|
+
chart6: "#06b6d4",
|
|
154
|
+
chart7: "#14b8a6",
|
|
155
|
+
chart8: "#f97316",
|
|
156
|
+
chart9: "#eab308",
|
|
157
|
+
chart10: "#22c55e",
|
|
158
|
+
chart11: "#0ea5e9",
|
|
159
|
+
chart12: "#f43f5e",
|
|
130
160
|
radius: "0.5rem",
|
|
131
161
|
};
|
|
132
162
|
|
|
@@ -163,11 +193,21 @@ export const NATURE_THEME: Theme = {
|
|
|
163
193
|
border: "#243824",
|
|
164
194
|
input: "#243824",
|
|
165
195
|
ring: "#4caf50",
|
|
196
|
+
toggleTrack: "#3d5a3d",
|
|
197
|
+
toggleTrackForeground: "#e8f5e9",
|
|
198
|
+
inputHover: "#4caf50",
|
|
166
199
|
chart1: "#4caf50",
|
|
167
200
|
chart2: "#66bb6a",
|
|
168
201
|
chart3: "#81c784",
|
|
169
202
|
chart4: "#a5d6a7",
|
|
170
203
|
chart5: "#c8e6c9",
|
|
204
|
+
chart6: "#8bc34a",
|
|
205
|
+
chart7: "#cddc39",
|
|
206
|
+
chart8: "#ffb74d",
|
|
207
|
+
chart9: "#4db6ac",
|
|
208
|
+
chart10: "#7986cb",
|
|
209
|
+
chart11: "#e57373",
|
|
210
|
+
chart12: "#64b5f6",
|
|
171
211
|
radius: "0.5rem",
|
|
172
212
|
};
|
|
173
213
|
|
|
@@ -204,11 +244,21 @@ export const SCIFI_THEME: Theme = {
|
|
|
204
244
|
border: "#30363d",
|
|
205
245
|
input: "#21262d",
|
|
206
246
|
ring: "#00d9ff",
|
|
247
|
+
toggleTrack: "#30363d",
|
|
248
|
+
toggleTrackForeground: "#c9d1d9",
|
|
249
|
+
inputHover: "#00d9ff",
|
|
207
250
|
chart1: "#00d9ff",
|
|
208
251
|
chart2: "#ff00ff",
|
|
209
252
|
chart3: "#00ff88",
|
|
210
253
|
chart4: "#ffd93d",
|
|
211
254
|
chart5: "#58a6ff",
|
|
255
|
+
chart6: "#ff6b6b",
|
|
256
|
+
chart7: "#4ecdc4",
|
|
257
|
+
chart8: "#a855f7",
|
|
258
|
+
chart9: "#fb923c",
|
|
259
|
+
chart10: "#38bdf8",
|
|
260
|
+
chart11: "#f472b6",
|
|
261
|
+
chart12: "#34d399",
|
|
212
262
|
radius: "0.5rem",
|
|
213
263
|
};
|
|
214
264
|
|
|
@@ -245,11 +295,21 @@ export const OCEAN_THEME: Theme = {
|
|
|
245
295
|
border: "#1e3a5f",
|
|
246
296
|
input: "#1e3a5f",
|
|
247
297
|
ring: "#2196f3",
|
|
298
|
+
toggleTrack: "#264a6e",
|
|
299
|
+
toggleTrackForeground: "#e3f2fd",
|
|
300
|
+
inputHover: "#2196f3",
|
|
248
301
|
chart1: "#2196f3",
|
|
249
302
|
chart2: "#00bcd4",
|
|
250
303
|
chart3: "#03a9f4",
|
|
251
304
|
chart4: "#0288d1",
|
|
252
305
|
chart5: "#0277bd",
|
|
306
|
+
chart6: "#26c6da",
|
|
307
|
+
chart7: "#42a5f5",
|
|
308
|
+
chart8: "#66bb6a",
|
|
309
|
+
chart9: "#ffb74d",
|
|
310
|
+
chart10: "#ef5350",
|
|
311
|
+
chart11: "#ab47bc",
|
|
312
|
+
chart12: "#78909c",
|
|
253
313
|
radius: "0.5rem",
|
|
254
314
|
};
|
|
255
315
|
|
|
@@ -286,11 +346,21 @@ export const SUNSET_THEME: Theme = {
|
|
|
286
346
|
border: "#241424",
|
|
287
347
|
input: "#241424",
|
|
288
348
|
ring: "#ff6b35",
|
|
349
|
+
toggleTrack: "#3d2a3d",
|
|
350
|
+
toggleTrackForeground: "#ffe0e6",
|
|
351
|
+
inputHover: "#ff6b35",
|
|
289
352
|
chart1: "#ff6b35",
|
|
290
353
|
chart2: "#c44569",
|
|
291
354
|
chart3: "#f7931e",
|
|
292
355
|
chart4: "#ffb703",
|
|
293
356
|
chart5: "#ff8c42",
|
|
357
|
+
chart6: "#06d6a0",
|
|
358
|
+
chart7: "#118ab2",
|
|
359
|
+
chart8: "#ef476f",
|
|
360
|
+
chart9: "#ffd166",
|
|
361
|
+
chart10: "#073b4c",
|
|
362
|
+
chart11: "#8338ec",
|
|
363
|
+
chart12: "#3a86ff",
|
|
294
364
|
radius: "0.5rem",
|
|
295
365
|
};
|
|
296
366
|
|
|
@@ -327,11 +397,21 @@ export const FOREST_THEME: Theme = {
|
|
|
327
397
|
border: "#1b5e20",
|
|
328
398
|
input: "#1a2e1a",
|
|
329
399
|
ring: "#2e7d32",
|
|
400
|
+
toggleTrack: "#2d4a2d",
|
|
401
|
+
toggleTrackForeground: "#e8f5e9",
|
|
402
|
+
inputHover: "#2e7d32",
|
|
330
403
|
chart1: "#2e7d32",
|
|
331
404
|
chart2: "#388e3c",
|
|
332
405
|
chart3: "#43a047",
|
|
333
406
|
chart4: "#66bb6a",
|
|
334
407
|
chart5: "#81c784",
|
|
408
|
+
chart6: "#a5d6a7",
|
|
409
|
+
chart7: "#c8e6c9",
|
|
410
|
+
chart8: "#ffb74d",
|
|
411
|
+
chart9: "#4db6ac",
|
|
412
|
+
chart10: "#7986cb",
|
|
413
|
+
chart11: "#e57373",
|
|
414
|
+
chart12: "#64b5f6",
|
|
335
415
|
radius: "0.5rem",
|
|
336
416
|
};
|
|
337
417
|
|
|
@@ -368,11 +448,21 @@ export const CYBERPUNK_THEME: Theme = {
|
|
|
368
448
|
border: "#1a1a2e",
|
|
369
449
|
input: "#16213e",
|
|
370
450
|
ring: "#ff00ff",
|
|
451
|
+
toggleTrack: "#2a2a4e",
|
|
452
|
+
toggleTrackForeground: "#f0f0f0",
|
|
453
|
+
inputHover: "#ff00ff",
|
|
371
454
|
chart1: "#ff00ff",
|
|
372
455
|
chart2: "#00ffff",
|
|
373
456
|
chart3: "#00ff88",
|
|
374
457
|
chart4: "#ffd700",
|
|
375
458
|
chart5: "#ff1744",
|
|
459
|
+
chart6: "#ff6b6b",
|
|
460
|
+
chart7: "#4ecdc4",
|
|
461
|
+
chart8: "#a855f7",
|
|
462
|
+
chart9: "#fb923c",
|
|
463
|
+
chart10: "#38bdf8",
|
|
464
|
+
chart11: "#84cc16",
|
|
465
|
+
chart12: "#f97316",
|
|
376
466
|
radius: "0.5rem",
|
|
377
467
|
};
|
|
378
468
|
|
|
@@ -407,13 +497,23 @@ export const MINIMALIST_LIGHT_THEME: Theme = {
|
|
|
407
497
|
chip: "#e9ecef",
|
|
408
498
|
chipForeground: "#495057",
|
|
409
499
|
border: "#dee2e6",
|
|
410
|
-
input: "#
|
|
500
|
+
input: "#dee2e6",
|
|
411
501
|
ring: "#000000",
|
|
502
|
+
toggleTrack: "#ced4da",
|
|
503
|
+
toggleTrackForeground: "#ffffff",
|
|
504
|
+
inputHover: "#6c757d",
|
|
412
505
|
chart1: "#000000",
|
|
413
506
|
chart2: "#6c757d",
|
|
414
507
|
chart3: "#adb5bd",
|
|
415
508
|
chart4: "#dee2e6",
|
|
416
509
|
chart5: "#e9ecef",
|
|
510
|
+
chart6: "#343a40",
|
|
511
|
+
chart7: "#495057",
|
|
512
|
+
chart8: "#868e96",
|
|
513
|
+
chart9: "#5c6bc0",
|
|
514
|
+
chart10: "#26a69a",
|
|
515
|
+
chart11: "#ef5350",
|
|
516
|
+
chart12: "#ffa726",
|
|
417
517
|
radius: "0.5rem",
|
|
418
518
|
};
|
|
419
519
|
|
|
@@ -450,11 +550,21 @@ export const DARK_ELEGANT_THEME: Theme = {
|
|
|
450
550
|
border: "#2d2d2d",
|
|
451
551
|
input: "#1e1e1e",
|
|
452
552
|
ring: "#bb86fc",
|
|
553
|
+
toggleTrack: "#3d3d3d",
|
|
554
|
+
toggleTrackForeground: "#e0e0e0",
|
|
555
|
+
inputHover: "#bb86fc",
|
|
453
556
|
chart1: "#bb86fc",
|
|
454
557
|
chart2: "#03dac6",
|
|
455
558
|
chart3: "#4caf50",
|
|
456
559
|
chart4: "#ff9800",
|
|
457
560
|
chart5: "#cf6679",
|
|
561
|
+
chart6: "#64b5f6",
|
|
562
|
+
chart7: "#81c784",
|
|
563
|
+
chart8: "#ffb74d",
|
|
564
|
+
chart9: "#e57373",
|
|
565
|
+
chart10: "#7986cb",
|
|
566
|
+
chart11: "#4db6ac",
|
|
567
|
+
chart12: "#f06292",
|
|
458
568
|
radius: "0.5rem",
|
|
459
569
|
};
|
|
460
570
|
|
package/src/theme/types.ts
CHANGED
|
@@ -54,12 +54,27 @@ export interface Theme {
|
|
|
54
54
|
input: string; // --input
|
|
55
55
|
ring: string; // --ring (focus ring)
|
|
56
56
|
|
|
57
|
+
/** Interactive control colors (optional, with fallbacks) */
|
|
58
|
+
toggleTrack?: string; // --toggle-track (switch track bg when off; fallback: muted)
|
|
59
|
+
toggleTrackForeground?: string; // --toggle-track-foreground (switch thumb when off; fallback: background)
|
|
60
|
+
inputHover?: string; // --input-hover (form control border on hover; fallback: derived)
|
|
61
|
+
|
|
57
62
|
/** Chart colors */
|
|
58
63
|
chart1: string; // --chart-1
|
|
59
64
|
chart2: string; // --chart-2
|
|
60
65
|
chart3: string; // --chart-3
|
|
61
66
|
chart4: string; // --chart-4
|
|
62
67
|
chart5: string; // --chart-5
|
|
68
|
+
chart6: string; // --chart-6
|
|
69
|
+
chart7: string; // --chart-7
|
|
70
|
+
chart8: string; // --chart-8
|
|
71
|
+
chart9: string; // --chart-9
|
|
72
|
+
chart10: string; // --chart-10
|
|
73
|
+
chart11: string; // --chart-11
|
|
74
|
+
chart12: string; // --chart-12
|
|
75
|
+
|
|
76
|
+
/** Disabled state */
|
|
77
|
+
disabledOpacity?: string; // --disabled-opacity (default: 0.5)
|
|
63
78
|
|
|
64
79
|
/** Border radius */
|
|
65
80
|
radius?: string; // --radius (default: 0.5rem)
|
|
@@ -105,11 +120,21 @@ export interface ThemeHSL extends Omit<Theme, keyof ThemeColorFields> {
|
|
|
105
120
|
border: string;
|
|
106
121
|
input: string;
|
|
107
122
|
ring: string;
|
|
123
|
+
toggleTrack?: string;
|
|
124
|
+
toggleTrackForeground?: string;
|
|
125
|
+
inputHover?: string;
|
|
108
126
|
chart1: string;
|
|
109
127
|
chart2: string;
|
|
110
128
|
chart3: string;
|
|
111
129
|
chart4: string;
|
|
112
130
|
chart5: string;
|
|
131
|
+
chart6: string;
|
|
132
|
+
chart7: string;
|
|
133
|
+
chart8: string;
|
|
134
|
+
chart9: string;
|
|
135
|
+
chart10: string;
|
|
136
|
+
chart11: string;
|
|
137
|
+
chart12: string;
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
type ThemeColorFields = {
|
|
@@ -138,11 +163,21 @@ type ThemeColorFields = {
|
|
|
138
163
|
border: string;
|
|
139
164
|
input: string;
|
|
140
165
|
ring: string;
|
|
166
|
+
toggleTrack?: string;
|
|
167
|
+
toggleTrackForeground?: string;
|
|
168
|
+
inputHover?: string;
|
|
141
169
|
chart1: string;
|
|
142
170
|
chart2: string;
|
|
143
171
|
chart3: string;
|
|
144
172
|
chart4: string;
|
|
145
173
|
chart5: string;
|
|
174
|
+
chart6: string;
|
|
175
|
+
chart7: string;
|
|
176
|
+
chart8: string;
|
|
177
|
+
chart9: string;
|
|
178
|
+
chart10: string;
|
|
179
|
+
chart11: string;
|
|
180
|
+
chart12: string;
|
|
146
181
|
};
|
|
147
182
|
|
|
148
183
|
/**
|
package/src/theme/utils.ts
CHANGED
|
@@ -85,14 +85,40 @@ export function themeToHsl(theme: Theme): ThemeHSL {
|
|
|
85
85
|
border: hexToHsl(theme.border),
|
|
86
86
|
input: hexToHsl(theme.input),
|
|
87
87
|
ring: hexToHsl(theme.ring),
|
|
88
|
+
toggleTrack: theme.toggleTrack ? hexToHsl(theme.toggleTrack) : undefined,
|
|
89
|
+
toggleTrackForeground: theme.toggleTrackForeground
|
|
90
|
+
? hexToHsl(theme.toggleTrackForeground)
|
|
91
|
+
: undefined,
|
|
92
|
+
inputHover: theme.inputHover ? hexToHsl(theme.inputHover) : undefined,
|
|
88
93
|
chart1: hexToHsl(theme.chart1),
|
|
89
94
|
chart2: hexToHsl(theme.chart2),
|
|
90
95
|
chart3: hexToHsl(theme.chart3),
|
|
91
96
|
chart4: hexToHsl(theme.chart4),
|
|
92
97
|
chart5: hexToHsl(theme.chart5),
|
|
98
|
+
chart6: hexToHsl(theme.chart6),
|
|
99
|
+
chart7: hexToHsl(theme.chart7),
|
|
100
|
+
chart8: hexToHsl(theme.chart8),
|
|
101
|
+
chart9: hexToHsl(theme.chart9),
|
|
102
|
+
chart10: hexToHsl(theme.chart10),
|
|
103
|
+
chart11: hexToHsl(theme.chart11),
|
|
104
|
+
chart12: hexToHsl(theme.chart12),
|
|
93
105
|
};
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Derive an input hover border color from the theme's HSL values.
|
|
110
|
+
* Blends toward the foreground for a subtle but visible hover effect.
|
|
111
|
+
*/
|
|
112
|
+
function deriveInputHoverHsl(hslTheme: ThemeHSL): string {
|
|
113
|
+
const parts = hslTheme.foreground.split(/\s+/);
|
|
114
|
+
if (parts.length >= 3) {
|
|
115
|
+
const h = parts[0];
|
|
116
|
+
const s = parts[1];
|
|
117
|
+
return `${h} ${s} 50%`;
|
|
118
|
+
}
|
|
119
|
+
return hslTheme.foreground;
|
|
120
|
+
}
|
|
121
|
+
|
|
96
122
|
/**
|
|
97
123
|
* Apply a theme to the DOM
|
|
98
124
|
*
|
|
@@ -135,11 +161,37 @@ export function applyTheme(theme: Theme, targetElement?: HTMLElement): void {
|
|
|
135
161
|
element.style.setProperty("--border", hslTheme.border);
|
|
136
162
|
element.style.setProperty("--input", hslTheme.input);
|
|
137
163
|
element.style.setProperty("--ring", hslTheme.ring);
|
|
164
|
+
|
|
165
|
+
// Interactive control tokens (with fallbacks)
|
|
166
|
+
element.style.setProperty(
|
|
167
|
+
"--toggle-track",
|
|
168
|
+
hslTheme.toggleTrack ?? hslTheme.muted
|
|
169
|
+
);
|
|
170
|
+
element.style.setProperty(
|
|
171
|
+
"--toggle-track-foreground",
|
|
172
|
+
hslTheme.toggleTrackForeground ?? hslTheme.background
|
|
173
|
+
);
|
|
174
|
+
element.style.setProperty(
|
|
175
|
+
"--input-hover",
|
|
176
|
+
hslTheme.inputHover ?? deriveInputHoverHsl(hslTheme)
|
|
177
|
+
);
|
|
178
|
+
|
|
138
179
|
element.style.setProperty("--chart-1", hslTheme.chart1);
|
|
139
180
|
element.style.setProperty("--chart-2", hslTheme.chart2);
|
|
140
181
|
element.style.setProperty("--chart-3", hslTheme.chart3);
|
|
141
182
|
element.style.setProperty("--chart-4", hslTheme.chart4);
|
|
142
183
|
element.style.setProperty("--chart-5", hslTheme.chart5);
|
|
184
|
+
element.style.setProperty("--chart-6", hslTheme.chart6);
|
|
185
|
+
element.style.setProperty("--chart-7", hslTheme.chart7);
|
|
186
|
+
element.style.setProperty("--chart-8", hslTheme.chart8);
|
|
187
|
+
element.style.setProperty("--chart-9", hslTheme.chart9);
|
|
188
|
+
element.style.setProperty("--chart-10", hslTheme.chart10);
|
|
189
|
+
element.style.setProperty("--chart-11", hslTheme.chart11);
|
|
190
|
+
element.style.setProperty("--chart-12", hslTheme.chart12);
|
|
191
|
+
|
|
192
|
+
if (theme.disabledOpacity) {
|
|
193
|
+
element.style.setProperty("--disabled-opacity", theme.disabledOpacity);
|
|
194
|
+
}
|
|
143
195
|
|
|
144
196
|
if (theme.radius) {
|
|
145
197
|
element.style.setProperty("--radius", theme.radius);
|
|
@@ -195,6 +247,13 @@ export function validateTheme(theme: unknown): theme is Theme {
|
|
|
195
247
|
"chart3",
|
|
196
248
|
"chart4",
|
|
197
249
|
"chart5",
|
|
250
|
+
"chart6",
|
|
251
|
+
"chart7",
|
|
252
|
+
"chart8",
|
|
253
|
+
"chart9",
|
|
254
|
+
"chart10",
|
|
255
|
+
"chart11",
|
|
256
|
+
"chart12",
|
|
198
257
|
];
|
|
199
258
|
|
|
200
259
|
for (const field of requiredFields) {
|
|
@@ -254,11 +313,21 @@ export function areThemesEqual(theme1: Theme, theme2: Theme): boolean {
|
|
|
254
313
|
"border",
|
|
255
314
|
"input",
|
|
256
315
|
"ring",
|
|
316
|
+
"toggleTrack",
|
|
317
|
+
"toggleTrackForeground",
|
|
318
|
+
"inputHover",
|
|
257
319
|
"chart1",
|
|
258
320
|
"chart2",
|
|
259
321
|
"chart3",
|
|
260
322
|
"chart4",
|
|
261
323
|
"chart5",
|
|
324
|
+
"chart6",
|
|
325
|
+
"chart7",
|
|
326
|
+
"chart8",
|
|
327
|
+
"chart9",
|
|
328
|
+
"chart10",
|
|
329
|
+
"chart11",
|
|
330
|
+
"chart12",
|
|
262
331
|
];
|
|
263
332
|
|
|
264
333
|
for (const field of colorFields) {
|
|
@@ -307,3 +376,165 @@ export function importTheme(jsonString: string): {
|
|
|
307
376
|
};
|
|
308
377
|
}
|
|
309
378
|
}
|
|
379
|
+
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Contrast validation utilities
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Parse a hex color string into linear-light sRGB components (0-1).
|
|
386
|
+
*/
|
|
387
|
+
function hexToLinearRgb(hex: string): [number, number, number] {
|
|
388
|
+
hex = hex.replace(/^#/, "");
|
|
389
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
390
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
391
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
392
|
+
|
|
393
|
+
const toLinear = (c: number) =>
|
|
394
|
+
c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
395
|
+
|
|
396
|
+
return [toLinear(r), toLinear(g), toLinear(b)];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Calculate WCAG 2.x relative luminance for a hex color.
|
|
401
|
+
* @see https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
|
402
|
+
*/
|
|
403
|
+
export function getRelativeLuminance(hex: string): number {
|
|
404
|
+
const [r, g, b] = hexToLinearRgb(hex);
|
|
405
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Calculate the WCAG contrast ratio between two hex colors.
|
|
410
|
+
* Returns a value >= 1, where 1 means no contrast and 21 is maximum.
|
|
411
|
+
* @see https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
|
412
|
+
*/
|
|
413
|
+
export function getContrastRatio(hex1: string, hex2: string): number {
|
|
414
|
+
const l1 = getRelativeLuminance(hex1);
|
|
415
|
+
const l2 = getRelativeLuminance(hex2);
|
|
416
|
+
const lighter = Math.max(l1, l2);
|
|
417
|
+
const darker = Math.min(l1, l2);
|
|
418
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface ContrastWarning {
|
|
422
|
+
pair: [string, string];
|
|
423
|
+
pairLabels: [string, string];
|
|
424
|
+
ratio: number;
|
|
425
|
+
required: number;
|
|
426
|
+
level: "fail" | "AA-large" | "AA";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Validate contrast ratios for critical color pairs in a theme.
|
|
431
|
+
*
|
|
432
|
+
* WCAG 2.1 requirements:
|
|
433
|
+
* - **3:1** minimum for UI components and large text (AA)
|
|
434
|
+
* - **4.5:1** minimum for normal text (AA)
|
|
435
|
+
*
|
|
436
|
+
* Returns an array of warnings for pairs that fall below the recommended
|
|
437
|
+
* minimums. An empty array means all checked pairs pass.
|
|
438
|
+
*/
|
|
439
|
+
export function validateThemeContrast(theme: Theme): ContrastWarning[] {
|
|
440
|
+
const warnings: ContrastWarning[] = [];
|
|
441
|
+
|
|
442
|
+
const uiPairs: {
|
|
443
|
+
a: string;
|
|
444
|
+
b: string;
|
|
445
|
+
labelA: string;
|
|
446
|
+
labelB: string;
|
|
447
|
+
minRatio: number;
|
|
448
|
+
}[] = [
|
|
449
|
+
// Text on background
|
|
450
|
+
{
|
|
451
|
+
a: theme.foreground,
|
|
452
|
+
b: theme.background,
|
|
453
|
+
labelA: "foreground",
|
|
454
|
+
labelB: "background",
|
|
455
|
+
minRatio: 4.5,
|
|
456
|
+
},
|
|
457
|
+
// Primary button text on primary bg
|
|
458
|
+
{
|
|
459
|
+
a: theme.primaryForeground,
|
|
460
|
+
b: theme.primary,
|
|
461
|
+
labelA: "primaryForeground",
|
|
462
|
+
labelB: "primary",
|
|
463
|
+
minRatio: 4.5,
|
|
464
|
+
},
|
|
465
|
+
// Accent foreground on accent
|
|
466
|
+
{
|
|
467
|
+
a: theme.accentForeground,
|
|
468
|
+
b: theme.accent,
|
|
469
|
+
labelA: "accentForeground",
|
|
470
|
+
labelB: "accent",
|
|
471
|
+
minRatio: 4.5,
|
|
472
|
+
},
|
|
473
|
+
// Input border vs background (UI component boundary — 3:1)
|
|
474
|
+
{
|
|
475
|
+
a: theme.input,
|
|
476
|
+
b: theme.background,
|
|
477
|
+
labelA: "input",
|
|
478
|
+
labelB: "background",
|
|
479
|
+
minRatio: 3,
|
|
480
|
+
},
|
|
481
|
+
// Toggle track vs background (UI component — 3:1)
|
|
482
|
+
{
|
|
483
|
+
a: theme.toggleTrack ?? theme.muted,
|
|
484
|
+
b: theme.background,
|
|
485
|
+
labelA: "toggleTrack",
|
|
486
|
+
labelB: "background",
|
|
487
|
+
minRatio: 3,
|
|
488
|
+
},
|
|
489
|
+
// Toggle thumb vs track (UI component — 3:1)
|
|
490
|
+
{
|
|
491
|
+
a: theme.toggleTrackForeground ?? theme.background,
|
|
492
|
+
b: theme.toggleTrack ?? theme.muted,
|
|
493
|
+
labelA: "toggleTrackForeground",
|
|
494
|
+
labelB: "toggleTrack",
|
|
495
|
+
minRatio: 3,
|
|
496
|
+
},
|
|
497
|
+
// Border vs background (UI component boundary — 3:1)
|
|
498
|
+
{
|
|
499
|
+
a: theme.border,
|
|
500
|
+
b: theme.background,
|
|
501
|
+
labelA: "border",
|
|
502
|
+
labelB: "background",
|
|
503
|
+
minRatio: 3,
|
|
504
|
+
},
|
|
505
|
+
// Muted foreground on muted bg
|
|
506
|
+
{
|
|
507
|
+
a: theme.mutedForeground,
|
|
508
|
+
b: theme.muted,
|
|
509
|
+
labelA: "mutedForeground",
|
|
510
|
+
labelB: "muted",
|
|
511
|
+
minRatio: 4.5,
|
|
512
|
+
},
|
|
513
|
+
// Destructive foreground on destructive bg
|
|
514
|
+
{
|
|
515
|
+
a: theme.destructiveForeground,
|
|
516
|
+
b: theme.destructive,
|
|
517
|
+
labelA: "destructiveForeground",
|
|
518
|
+
labelB: "destructive",
|
|
519
|
+
minRatio: 4.5,
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
for (const { a, b, labelA, labelB, minRatio } of uiPairs) {
|
|
524
|
+
const ratio = getContrastRatio(a, b);
|
|
525
|
+
if (ratio < minRatio) {
|
|
526
|
+
let level: ContrastWarning["level"] = "fail";
|
|
527
|
+
if (ratio >= 3) level = "AA-large";
|
|
528
|
+
|
|
529
|
+
warnings.push({
|
|
530
|
+
pair: [a, b],
|
|
531
|
+
pairLabels: [labelA, labelB],
|
|
532
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
533
|
+
required: minRatio,
|
|
534
|
+
level,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return warnings;
|
|
540
|
+
}
|