@lessonkit/themes 1.2.0 → 1.3.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 +36 -8
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +35 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
lightTheme: () => lightTheme,
|
|
37
37
|
mergeThemes: () => mergeThemes,
|
|
38
38
|
radiusVarName: () => radiusVarName,
|
|
39
|
+
sanitizeCssCustomPropertyValue: () => sanitizeCssCustomPropertyValue,
|
|
39
40
|
shadowVarName: () => shadowVarName,
|
|
40
41
|
spacingVarName: () => spacingVarName,
|
|
41
42
|
themeToCssDeclarationBlock: () => themeToCssDeclarationBlock,
|
|
@@ -68,9 +69,20 @@ var TYPOGRAPHY_KEYS = [
|
|
|
68
69
|
];
|
|
69
70
|
var RADIUS_KEYS = ["sm", "md", "lg"];
|
|
70
71
|
var SHADOW_KEYS = ["sm", "md", "lg"];
|
|
72
|
+
var EXTRA_COLOR_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
71
73
|
function isNonEmptyString(v) {
|
|
72
74
|
return typeof v === "string" && v.trim().length > 0;
|
|
73
75
|
}
|
|
76
|
+
function isSafeCssCustomPropertyValue(value) {
|
|
77
|
+
return !/[;}\r\n]/.test(value) && !value.includes("/*");
|
|
78
|
+
}
|
|
79
|
+
function validateCssTokenValue(path, value, issues) {
|
|
80
|
+
if (!isSafeCssCustomPropertyValue(value)) {
|
|
81
|
+
issues.push({ path, message: "must not contain CSS-breaking characters (;, }, newlines, or comments)" });
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
74
86
|
function validateRequiredGroup(group, value, keys, issues) {
|
|
75
87
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
76
88
|
issues.push({ path: group, message: "must be an object" });
|
|
@@ -82,7 +94,7 @@ function validateRequiredGroup(group, value, keys, issues) {
|
|
|
82
94
|
const v = obj[key];
|
|
83
95
|
if (!isNonEmptyString(v)) {
|
|
84
96
|
issues.push({ path: `${group}.${key}`, message: "required non-empty string" });
|
|
85
|
-
} else {
|
|
97
|
+
} else if (validateCssTokenValue(`${group}.${key}`, v, issues)) {
|
|
86
98
|
out[key] = v;
|
|
87
99
|
}
|
|
88
100
|
}
|
|
@@ -96,9 +108,16 @@ function validateColorsExtra(value, issues) {
|
|
|
96
108
|
}
|
|
97
109
|
const extra = {};
|
|
98
110
|
for (const [k, v] of Object.entries(value)) {
|
|
111
|
+
if (!EXTRA_COLOR_KEY_PATTERN.test(k)) {
|
|
112
|
+
issues.push({
|
|
113
|
+
path: `colors.extra.${k}`,
|
|
114
|
+
message: "key must match [a-zA-Z][a-zA-Z0-9_-]*"
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
99
118
|
if (!isNonEmptyString(v)) {
|
|
100
119
|
issues.push({ path: `colors.extra.${k}`, message: "must be a non-empty string" });
|
|
101
|
-
} else {
|
|
120
|
+
} else if (validateCssTokenValue(`colors.extra.${k}`, v, issues)) {
|
|
102
121
|
extra[k] = v;
|
|
103
122
|
}
|
|
104
123
|
}
|
|
@@ -204,6 +223,14 @@ function mergeThemes(base, ...overrides) {
|
|
|
204
223
|
}
|
|
205
224
|
|
|
206
225
|
// src/cssVariables.ts
|
|
226
|
+
function sanitizeCssCustomPropertyValue(value) {
|
|
227
|
+
if (/[;}\r\n]/.test(value) || value.includes("/*")) return null;
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
function assignCssVar(vars, key, value) {
|
|
231
|
+
const safe = sanitizeCssCustomPropertyValue(value);
|
|
232
|
+
if (safe !== null) vars[key] = safe;
|
|
233
|
+
}
|
|
207
234
|
function tokenKeyToKebab(key) {
|
|
208
235
|
return key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
209
236
|
}
|
|
@@ -230,23 +257,23 @@ function themeToCssVariables(theme) {
|
|
|
230
257
|
for (const [key, value] of Object.entries(theme.colors)) {
|
|
231
258
|
if (key === "extra" && value && typeof value === "object") {
|
|
232
259
|
for (const [ek, ev] of Object.entries(value)) {
|
|
233
|
-
vars
|
|
260
|
+
assignCssVar(vars, colorExtraVarName(ek), ev);
|
|
234
261
|
}
|
|
235
262
|
} else if (key !== "extra") {
|
|
236
|
-
vars
|
|
263
|
+
assignCssVar(vars, colorVarName(key), value);
|
|
237
264
|
}
|
|
238
265
|
}
|
|
239
266
|
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
240
|
-
vars
|
|
267
|
+
assignCssVar(vars, spacingVarName(key), value);
|
|
241
268
|
}
|
|
242
269
|
for (const [key, value] of Object.entries(theme.typography)) {
|
|
243
|
-
vars
|
|
270
|
+
assignCssVar(vars, typographyVarName(key), value);
|
|
244
271
|
}
|
|
245
272
|
for (const [key, value] of Object.entries(theme.radius)) {
|
|
246
|
-
vars
|
|
273
|
+
assignCssVar(vars, radiusVarName(key), value);
|
|
247
274
|
}
|
|
248
275
|
for (const [key, value] of Object.entries(theme.shadows)) {
|
|
249
|
-
vars
|
|
276
|
+
assignCssVar(vars, shadowVarName(key), value);
|
|
250
277
|
}
|
|
251
278
|
const sorted = {};
|
|
252
279
|
for (const key of Object.keys(vars).sort()) {
|
|
@@ -492,6 +519,7 @@ function buildThemeCatalog() {
|
|
|
492
519
|
lightTheme,
|
|
493
520
|
mergeThemes,
|
|
494
521
|
radiusVarName,
|
|
522
|
+
sanitizeCssCustomPropertyValue,
|
|
495
523
|
shadowVarName,
|
|
496
524
|
spacingVarName,
|
|
497
525
|
themeToCssDeclarationBlock,
|
package/dist/index.d.cts
CHANGED
|
@@ -53,6 +53,8 @@ declare const REQUIRED_SHADOW_KEYS: ThemeShadowKey[];
|
|
|
53
53
|
*/
|
|
54
54
|
declare function mergeThemes(base: LessonkitThemeV1, ...overrides: (PartialLessonkitThemeV1 | undefined)[]): LessonkitThemeV1;
|
|
55
55
|
|
|
56
|
+
/** Reject values that could break out of a CSS declaration block. */
|
|
57
|
+
declare function sanitizeCssCustomPropertyValue(value: string): string | null;
|
|
56
58
|
/** Map a token path segment to kebab-case for CSS custom property names. */
|
|
57
59
|
declare function tokenKeyToKebab(key: string): string;
|
|
58
60
|
/** CSS custom property name for a color token (required keys). */
|
|
@@ -93,4 +95,4 @@ type ThemeCatalogEntry = {
|
|
|
93
95
|
/** Enumerable catalog of themeable tokens (v1). */
|
|
94
96
|
declare function buildThemeCatalog(): ThemeCatalogEntry[];
|
|
95
97
|
|
|
96
|
-
export { type LessonkitTheme, type LessonkitThemeColors, type LessonkitThemeRadius, type LessonkitThemeShadows, type LessonkitThemeSpacing, type LessonkitThemeTypography, type LessonkitThemeV1, type PartialLessonkitThemeV1, REQUIRED_COLOR_KEYS, REQUIRED_RADIUS_KEYS, REQUIRED_SHADOW_KEYS, REQUIRED_SPACING_KEYS, REQUIRED_TYPOGRAPHY_KEYS, type ThemeCatalogEntry, type ThemeColorKey, type ThemePresetName, type ThemeRadiusKey, type ThemeShadowKey, type ThemeSpacingKey, type ThemeTypographyKey, type ThemeValidationIssue, type ThemeValidationResult, brandTheme, brandThemeOverrides, buildThemeCatalog, colorExtraVarName, colorVarName, darkTheme, defaultTheme, getPresetTheme, lightTheme, mergeThemes, radiusVarName, shadowVarName, spacingVarName, themeToCssDeclarationBlock, themeToCssVariables, tokenKeyToKebab, typographyVarName, validateTheme };
|
|
98
|
+
export { type LessonkitTheme, type LessonkitThemeColors, type LessonkitThemeRadius, type LessonkitThemeShadows, type LessonkitThemeSpacing, type LessonkitThemeTypography, type LessonkitThemeV1, type PartialLessonkitThemeV1, REQUIRED_COLOR_KEYS, REQUIRED_RADIUS_KEYS, REQUIRED_SHADOW_KEYS, REQUIRED_SPACING_KEYS, REQUIRED_TYPOGRAPHY_KEYS, type ThemeCatalogEntry, type ThemeColorKey, type ThemePresetName, type ThemeRadiusKey, type ThemeShadowKey, type ThemeSpacingKey, type ThemeTypographyKey, type ThemeValidationIssue, type ThemeValidationResult, brandTheme, brandThemeOverrides, buildThemeCatalog, colorExtraVarName, colorVarName, darkTheme, defaultTheme, getPresetTheme, lightTheme, mergeThemes, radiusVarName, sanitizeCssCustomPropertyValue, shadowVarName, spacingVarName, themeToCssDeclarationBlock, themeToCssVariables, tokenKeyToKebab, typographyVarName, validateTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ declare const REQUIRED_SHADOW_KEYS: ThemeShadowKey[];
|
|
|
53
53
|
*/
|
|
54
54
|
declare function mergeThemes(base: LessonkitThemeV1, ...overrides: (PartialLessonkitThemeV1 | undefined)[]): LessonkitThemeV1;
|
|
55
55
|
|
|
56
|
+
/** Reject values that could break out of a CSS declaration block. */
|
|
57
|
+
declare function sanitizeCssCustomPropertyValue(value: string): string | null;
|
|
56
58
|
/** Map a token path segment to kebab-case for CSS custom property names. */
|
|
57
59
|
declare function tokenKeyToKebab(key: string): string;
|
|
58
60
|
/** CSS custom property name for a color token (required keys). */
|
|
@@ -93,4 +95,4 @@ type ThemeCatalogEntry = {
|
|
|
93
95
|
/** Enumerable catalog of themeable tokens (v1). */
|
|
94
96
|
declare function buildThemeCatalog(): ThemeCatalogEntry[];
|
|
95
97
|
|
|
96
|
-
export { type LessonkitTheme, type LessonkitThemeColors, type LessonkitThemeRadius, type LessonkitThemeShadows, type LessonkitThemeSpacing, type LessonkitThemeTypography, type LessonkitThemeV1, type PartialLessonkitThemeV1, REQUIRED_COLOR_KEYS, REQUIRED_RADIUS_KEYS, REQUIRED_SHADOW_KEYS, REQUIRED_SPACING_KEYS, REQUIRED_TYPOGRAPHY_KEYS, type ThemeCatalogEntry, type ThemeColorKey, type ThemePresetName, type ThemeRadiusKey, type ThemeShadowKey, type ThemeSpacingKey, type ThemeTypographyKey, type ThemeValidationIssue, type ThemeValidationResult, brandTheme, brandThemeOverrides, buildThemeCatalog, colorExtraVarName, colorVarName, darkTheme, defaultTheme, getPresetTheme, lightTheme, mergeThemes, radiusVarName, shadowVarName, spacingVarName, themeToCssDeclarationBlock, themeToCssVariables, tokenKeyToKebab, typographyVarName, validateTheme };
|
|
98
|
+
export { type LessonkitTheme, type LessonkitThemeColors, type LessonkitThemeRadius, type LessonkitThemeShadows, type LessonkitThemeSpacing, type LessonkitThemeTypography, type LessonkitThemeV1, type PartialLessonkitThemeV1, REQUIRED_COLOR_KEYS, REQUIRED_RADIUS_KEYS, REQUIRED_SHADOW_KEYS, REQUIRED_SPACING_KEYS, REQUIRED_TYPOGRAPHY_KEYS, type ThemeCatalogEntry, type ThemeColorKey, type ThemePresetName, type ThemeRadiusKey, type ThemeShadowKey, type ThemeSpacingKey, type ThemeTypographyKey, type ThemeValidationIssue, type ThemeValidationResult, brandTheme, brandThemeOverrides, buildThemeCatalog, colorExtraVarName, colorVarName, darkTheme, defaultTheme, getPresetTheme, lightTheme, mergeThemes, radiusVarName, sanitizeCssCustomPropertyValue, shadowVarName, spacingVarName, themeToCssDeclarationBlock, themeToCssVariables, tokenKeyToKebab, typographyVarName, validateTheme };
|
package/dist/index.js
CHANGED
|
@@ -20,9 +20,20 @@ var TYPOGRAPHY_KEYS = [
|
|
|
20
20
|
];
|
|
21
21
|
var RADIUS_KEYS = ["sm", "md", "lg"];
|
|
22
22
|
var SHADOW_KEYS = ["sm", "md", "lg"];
|
|
23
|
+
var EXTRA_COLOR_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
23
24
|
function isNonEmptyString(v) {
|
|
24
25
|
return typeof v === "string" && v.trim().length > 0;
|
|
25
26
|
}
|
|
27
|
+
function isSafeCssCustomPropertyValue(value) {
|
|
28
|
+
return !/[;}\r\n]/.test(value) && !value.includes("/*");
|
|
29
|
+
}
|
|
30
|
+
function validateCssTokenValue(path, value, issues) {
|
|
31
|
+
if (!isSafeCssCustomPropertyValue(value)) {
|
|
32
|
+
issues.push({ path, message: "must not contain CSS-breaking characters (;, }, newlines, or comments)" });
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
26
37
|
function validateRequiredGroup(group, value, keys, issues) {
|
|
27
38
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
28
39
|
issues.push({ path: group, message: "must be an object" });
|
|
@@ -34,7 +45,7 @@ function validateRequiredGroup(group, value, keys, issues) {
|
|
|
34
45
|
const v = obj[key];
|
|
35
46
|
if (!isNonEmptyString(v)) {
|
|
36
47
|
issues.push({ path: `${group}.${key}`, message: "required non-empty string" });
|
|
37
|
-
} else {
|
|
48
|
+
} else if (validateCssTokenValue(`${group}.${key}`, v, issues)) {
|
|
38
49
|
out[key] = v;
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -48,9 +59,16 @@ function validateColorsExtra(value, issues) {
|
|
|
48
59
|
}
|
|
49
60
|
const extra = {};
|
|
50
61
|
for (const [k, v] of Object.entries(value)) {
|
|
62
|
+
if (!EXTRA_COLOR_KEY_PATTERN.test(k)) {
|
|
63
|
+
issues.push({
|
|
64
|
+
path: `colors.extra.${k}`,
|
|
65
|
+
message: "key must match [a-zA-Z][a-zA-Z0-9_-]*"
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
51
69
|
if (!isNonEmptyString(v)) {
|
|
52
70
|
issues.push({ path: `colors.extra.${k}`, message: "must be a non-empty string" });
|
|
53
|
-
} else {
|
|
71
|
+
} else if (validateCssTokenValue(`colors.extra.${k}`, v, issues)) {
|
|
54
72
|
extra[k] = v;
|
|
55
73
|
}
|
|
56
74
|
}
|
|
@@ -156,6 +174,14 @@ function mergeThemes(base, ...overrides) {
|
|
|
156
174
|
}
|
|
157
175
|
|
|
158
176
|
// src/cssVariables.ts
|
|
177
|
+
function sanitizeCssCustomPropertyValue(value) {
|
|
178
|
+
if (/[;}\r\n]/.test(value) || value.includes("/*")) return null;
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function assignCssVar(vars, key, value) {
|
|
182
|
+
const safe = sanitizeCssCustomPropertyValue(value);
|
|
183
|
+
if (safe !== null) vars[key] = safe;
|
|
184
|
+
}
|
|
159
185
|
function tokenKeyToKebab(key) {
|
|
160
186
|
return key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
161
187
|
}
|
|
@@ -182,23 +208,23 @@ function themeToCssVariables(theme) {
|
|
|
182
208
|
for (const [key, value] of Object.entries(theme.colors)) {
|
|
183
209
|
if (key === "extra" && value && typeof value === "object") {
|
|
184
210
|
for (const [ek, ev] of Object.entries(value)) {
|
|
185
|
-
vars
|
|
211
|
+
assignCssVar(vars, colorExtraVarName(ek), ev);
|
|
186
212
|
}
|
|
187
213
|
} else if (key !== "extra") {
|
|
188
|
-
vars
|
|
214
|
+
assignCssVar(vars, colorVarName(key), value);
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
217
|
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
192
|
-
vars
|
|
218
|
+
assignCssVar(vars, spacingVarName(key), value);
|
|
193
219
|
}
|
|
194
220
|
for (const [key, value] of Object.entries(theme.typography)) {
|
|
195
|
-
vars
|
|
221
|
+
assignCssVar(vars, typographyVarName(key), value);
|
|
196
222
|
}
|
|
197
223
|
for (const [key, value] of Object.entries(theme.radius)) {
|
|
198
|
-
vars
|
|
224
|
+
assignCssVar(vars, radiusVarName(key), value);
|
|
199
225
|
}
|
|
200
226
|
for (const [key, value] of Object.entries(theme.shadows)) {
|
|
201
|
-
vars
|
|
227
|
+
assignCssVar(vars, shadowVarName(key), value);
|
|
202
228
|
}
|
|
203
229
|
const sorted = {};
|
|
204
230
|
for (const key of Object.keys(vars).sort()) {
|
|
@@ -443,6 +469,7 @@ export {
|
|
|
443
469
|
lightTheme,
|
|
444
470
|
mergeThemes,
|
|
445
471
|
radiusVarName,
|
|
472
|
+
sanitizeCssCustomPropertyValue,
|
|
446
473
|
shadowVarName,
|
|
447
474
|
spacingVarName,
|
|
448
475
|
themeToCssDeclarationBlock,
|