clampography 2.0.0-beta.13 → 2.0.0-beta.15
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/package.json +2 -2
- package/src/convert.js +2 -2
- package/src/extra.js +3 -1
- package/src/index.js +140 -94
- package/src/theme-plugin.js +21 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clampography",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.15",
|
|
4
4
|
"description": "Fluid typography system based on CSS clamp() with optional themes and extra styles. A feature-rich alternative to Tailwind CSS Typography plugin.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -45,6 +45,6 @@
|
|
|
45
45
|
"tailwindcss": ">=4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"tailwindcss": "^4.
|
|
48
|
+
"tailwindcss": "^4.1.18"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/convert.js
CHANGED
|
@@ -171,13 +171,13 @@ try {
|
|
|
171
171
|
console.log(`📖 Reading ${INPUT_FILE}...`);
|
|
172
172
|
const cssContent = readFileSync(resolve(INPUT_FILE), "utf-8");
|
|
173
173
|
|
|
174
|
-
console.log("
|
|
174
|
+
console.log("✨ Converting CSS to JS...");
|
|
175
175
|
const jsContent = convertCSStoJS(cssContent);
|
|
176
176
|
|
|
177
177
|
console.log(`💾 Writing ${OUTPUT_FILE}...`);
|
|
178
178
|
writeFileSync(resolve(OUTPUT_FILE), jsContent, "utf-8");
|
|
179
179
|
|
|
180
|
-
console.log("
|
|
180
|
+
console.log("✔️ Conversion complete!");
|
|
181
181
|
console.log(`📄 Output: ${OUTPUT_FILE}`);
|
|
182
182
|
} catch (error) {
|
|
183
183
|
console.error("❌ Error:", error.message);
|
package/src/extra.js
CHANGED
|
@@ -17,6 +17,8 @@ export default {
|
|
|
17
17
|
"text-decoration-line": "underline",
|
|
18
18
|
"text-decoration-thickness": "2px",
|
|
19
19
|
"text-underline-offset": "4px",
|
|
20
|
+
"text-decoration-color":
|
|
21
|
+
"color-mix(in oklab, var(--clampography-link) 30%, transparent)",
|
|
20
22
|
"transition-property": "color, text-decoration-color",
|
|
21
23
|
"transition-duration": "150ms",
|
|
22
24
|
},
|
|
@@ -35,7 +37,7 @@ export default {
|
|
|
35
37
|
},
|
|
36
38
|
|
|
37
39
|
// Inline Code
|
|
38
|
-
":where(code, kbd, samp)": {
|
|
40
|
+
":where(code:not(pre code), kbd, samp)": {
|
|
39
41
|
"background-color": "var(--clampography-surface)",
|
|
40
42
|
"color": "var(--clampography-heading)",
|
|
41
43
|
"border": "1px solid var(--clampography-border)",
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import { themes as builtInThemes } from "./themes.js";
|
|
|
3
3
|
import baseStyles from "./base.js";
|
|
4
4
|
import extraStyles from "./extra.js";
|
|
5
5
|
|
|
6
|
+
// Import version from package.json
|
|
7
|
+
import { version } from "../package.json" with { type: "json" };
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* Helper to resolve boolean options from CSS configuration.
|
|
8
11
|
* CSS values often come as strings ("true"/"false"), which are both truthy in JS.
|
|
@@ -17,111 +20,154 @@ const resolveBool = (value, defaultValue) => {
|
|
|
17
20
|
* Main plugin function.
|
|
18
21
|
*/
|
|
19
22
|
export default plugin.withOptions(
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let themesToInclude = [];
|
|
33
|
-
let defaultThemeName = null;
|
|
34
|
-
let prefersDarkTheme = false;
|
|
35
|
-
let rootSelector = options.root ?? ":root";
|
|
36
|
-
let isAllThemes = false; // Track if user specified "all"
|
|
37
|
-
|
|
38
|
-
// Normalize input to an array of strings
|
|
39
|
-
let rawThemeList = [];
|
|
40
|
-
|
|
41
|
-
if (typeof configThemes === "string") {
|
|
42
|
-
if (["all", "true", "yes"].includes(configThemes.trim())) {
|
|
43
|
-
// Special case: themes: all
|
|
44
|
-
isAllThemes = true;
|
|
45
|
-
rawThemeList = Object.keys(builtInThemes);
|
|
46
|
-
} else if (["false", "none", "no"].includes(configThemes.trim())) {
|
|
47
|
-
// Explicitly disabled themes
|
|
48
|
-
rawThemeList = [];
|
|
49
|
-
} else {
|
|
50
|
-
rawThemeList = configThemes.split(",");
|
|
51
|
-
}
|
|
52
|
-
} else if (Array.isArray(configThemes)) {
|
|
53
|
-
rawThemeList = configThemes;
|
|
54
|
-
} else {
|
|
55
|
-
// Default behavior: NO themes loaded automatically.
|
|
56
|
-
// User must specify themes to load them.
|
|
57
|
-
rawThemeList = [];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
61
|
-
rawThemeList.forEach((rawItem) => {
|
|
62
|
-
let themeName = rawItem.trim();
|
|
63
|
-
|
|
64
|
-
// Ignore empty entries
|
|
65
|
-
if (!themeName) return;
|
|
66
|
-
|
|
67
|
-
// Check for --default flag
|
|
68
|
-
if (themeName.includes("--default")) {
|
|
69
|
-
themeName = themeName.replace("--default", "").trim();
|
|
70
|
-
defaultThemeName = themeName;
|
|
23
|
+
(() => {
|
|
24
|
+
let firstRun = true; // Track first run for logging
|
|
25
|
+
|
|
26
|
+
return (options = {}) => {
|
|
27
|
+
return ({ addBase }) => {
|
|
28
|
+
// Extract logs option (default: true)
|
|
29
|
+
const showLogs = resolveBool(options.logs, true);
|
|
30
|
+
|
|
31
|
+
// Show startup log only once
|
|
32
|
+
if (showLogs && firstRun) {
|
|
33
|
+
console.log(`🍀 Clampography v${version} loaded successfully`);
|
|
34
|
+
firstRun = false;
|
|
71
35
|
}
|
|
72
36
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
37
|
+
// 1. Load Base and Extra styles
|
|
38
|
+
// We use the helper to correctly parse "false" string from CSS
|
|
39
|
+
const includeBase = resolveBool(options.base, true); // Default: true
|
|
40
|
+
const includeExtra = resolveBool(options.extra, false); // Default: false
|
|
41
|
+
|
|
42
|
+
includeBase && addBase(baseStyles);
|
|
43
|
+
includeExtra && addBase(extraStyles);
|
|
44
|
+
|
|
45
|
+
// 2. Parse themes configuration
|
|
46
|
+
let configThemes = options.themes;
|
|
47
|
+
let themesToInclude = [];
|
|
48
|
+
let defaultThemeName = null;
|
|
49
|
+
let prefersDarkTheme = false;
|
|
50
|
+
let rootSelector = options.root ?? ":root";
|
|
51
|
+
let isAllThemes = false; // Track if user specified "all"
|
|
52
|
+
|
|
53
|
+
// Normalize input to an array of strings
|
|
54
|
+
let rawThemeList = [];
|
|
55
|
+
if (typeof configThemes === "string") {
|
|
56
|
+
if (["all", "true", "yes"].includes(configThemes.trim())) {
|
|
57
|
+
// Special case: themes: all
|
|
58
|
+
isAllThemes = true;
|
|
59
|
+
rawThemeList = Object.keys(builtInThemes);
|
|
60
|
+
} else if (["false", "none", "no"].includes(configThemes.trim())) {
|
|
61
|
+
// Explicitly disabled themes
|
|
62
|
+
rawThemeList = [];
|
|
63
|
+
} else {
|
|
64
|
+
rawThemeList = configThemes.split(",");
|
|
65
|
+
}
|
|
66
|
+
} else if (Array.isArray(configThemes)) {
|
|
67
|
+
rawThemeList = configThemes;
|
|
68
|
+
} else {
|
|
69
|
+
// Default behavior: NO themes loaded automatically.
|
|
70
|
+
// User must specify themes to load them.
|
|
71
|
+
rawThemeList = [];
|
|
77
72
|
}
|
|
78
73
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
75
|
+
rawThemeList.forEach((rawItem) => {
|
|
76
|
+
let themeName = rawItem.trim();
|
|
77
|
+
|
|
78
|
+
// Ignore empty entries
|
|
79
|
+
if (!themeName) return;
|
|
80
|
+
|
|
81
|
+
// Check for --default flag
|
|
82
|
+
if (themeName.includes("--default")) {
|
|
83
|
+
themeName = themeName.replace("--default", "").trim();
|
|
84
|
+
defaultThemeName = themeName;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for --prefersdark flag
|
|
88
|
+
if (themeName.toLowerCase().includes("--prefersdark")) {
|
|
89
|
+
themeName = themeName.replace(/--prefersdark/i, "").trim();
|
|
90
|
+
prefersDarkTheme = themeName;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if theme exists in the database
|
|
94
|
+
if (builtInThemes[themeName]) {
|
|
95
|
+
themesToInclude.push(themeName);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// If list is empty after filtering, stop here
|
|
100
|
+
if (
|
|
101
|
+
themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
|
|
102
|
+
) return;
|
|
103
|
+
|
|
104
|
+
// 4. Auto-configure defaults for "themes: all"
|
|
105
|
+
// If user didn't specify --default or --prefersdark flags,
|
|
106
|
+
// automatically set light as default and dark for prefers-color-scheme
|
|
107
|
+
if (isAllThemes) {
|
|
108
|
+
if (!defaultThemeName && themesToInclude.includes("light")) {
|
|
109
|
+
defaultThemeName = "light";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!prefersDarkTheme && themesToInclude.includes("dark")) {
|
|
113
|
+
prefersDarkTheme = "dark";
|
|
114
|
+
}
|
|
82
115
|
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// If list is empty after filtering, stop here
|
|
86
|
-
if (
|
|
87
|
-
themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
|
|
88
|
-
) return;
|
|
89
|
-
|
|
90
|
-
// 4. Auto-configure defaults for "themes: all"
|
|
91
|
-
// If user didn't specify --default or --prefersdark flags,
|
|
92
|
-
// automatically set light as default and dark for prefers-color-scheme
|
|
93
|
-
if (isAllThemes) {
|
|
94
|
-
if (!defaultThemeName && themesToInclude.includes("light")) {
|
|
95
|
-
defaultThemeName = "light";
|
|
96
|
-
}
|
|
97
|
-
if (!prefersDarkTheme && themesToInclude.includes("dark")) {
|
|
98
|
-
prefersDarkTheme = "dark";
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
116
|
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
// 5. Generate CSS
|
|
118
|
+
const themeStyles = {};
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
// A. Default theme - uses :where() for lower specificity
|
|
121
|
+
if (defaultThemeName && builtInThemes[defaultThemeName]) {
|
|
122
|
+
const defaultSelector =
|
|
123
|
+
`:where(${rootSelector}),[data-theme="${defaultThemeName}"]`;
|
|
124
|
+
themeStyles[defaultSelector] = builtInThemes[defaultThemeName];
|
|
125
|
+
}
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
// B. Theme for prefers-color-scheme: dark - only applies to root selector
|
|
128
|
+
if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
|
|
129
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
130
|
+
[rootSelector]: builtInThemes[prefersDarkTheme],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
116
133
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
// C. All themes available via [data-theme] attribute
|
|
135
|
+
// WITH higher specificity to override media query
|
|
136
|
+
themesToInclude.forEach((themeName) => {
|
|
137
|
+
// Skip if already added as default (to avoid duplication)
|
|
138
|
+
if (themeName === defaultThemeName) return;
|
|
139
|
+
|
|
140
|
+
const selector =
|
|
141
|
+
`html[data-theme="${themeName}"], [data-theme="${themeName}"]`;
|
|
142
|
+
themeStyles[selector] = builtInThemes[themeName];
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ✅ D. CRITICAL: Override media query with data-theme selectors
|
|
146
|
+
// This ensures manual theme selection always wins over system preference
|
|
147
|
+
if (prefersDarkTheme) {
|
|
148
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
149
|
+
...themeStyles["@media (prefers-color-scheme: dark)"],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Add all themes inside media query to override the :root rule
|
|
153
|
+
themesToInclude.forEach((themeName) => {
|
|
154
|
+
if (themeName === prefersDarkTheme) return; // Skip the prefersDark theme itself
|
|
155
|
+
|
|
156
|
+
const selector =
|
|
157
|
+
`html[data-theme="${themeName}"], [data-theme="${themeName}"]`;
|
|
158
|
+
|
|
159
|
+
// Add inside media query
|
|
160
|
+
if (!themeStyles["@media (prefers-color-scheme: dark)"][selector]) {
|
|
161
|
+
themeStyles["@media (prefers-color-scheme: dark)"][selector] =
|
|
162
|
+
builtInThemes[themeName];
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
121
166
|
|
|
122
|
-
|
|
167
|
+
addBase(themeStyles);
|
|
168
|
+
};
|
|
123
169
|
};
|
|
124
|
-
},
|
|
170
|
+
})(),
|
|
125
171
|
// Theme extension - enables utilities like bg-surface, text-heading, etc.
|
|
126
172
|
(options = {}) => {
|
|
127
173
|
return {
|
package/src/theme-plugin.js
CHANGED
|
@@ -8,11 +8,17 @@ export default plugin.withOptions((options = {}) => {
|
|
|
8
8
|
const isDefault = options.default ?? false;
|
|
9
9
|
const isPrefersDark = options.prefersdark ?? false;
|
|
10
10
|
const rootSelector = options.root ?? ":root";
|
|
11
|
-
// Defaults to light scheme if not specified
|
|
12
11
|
const colorScheme = options["color-scheme"] ?? "light";
|
|
13
12
|
|
|
13
|
+
// This option is completely separate from the main plugin's logs option
|
|
14
|
+
const showLogs = options.logs !== false;
|
|
15
|
+
|
|
14
16
|
if (!themeName) {
|
|
15
|
-
|
|
17
|
+
if (showLogs) {
|
|
18
|
+
console.warn(
|
|
19
|
+
"🍀 Clampography: Theme definition missing 'name' property.",
|
|
20
|
+
);
|
|
21
|
+
}
|
|
16
22
|
return;
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -50,14 +56,15 @@ export default plugin.withOptions((options = {}) => {
|
|
|
50
56
|
Object.keys(options).forEach((key) => {
|
|
51
57
|
// Ignore metadata keys
|
|
52
58
|
if (
|
|
53
|
-
["name", "default", "prefersdark", "root", "color-scheme"]
|
|
59
|
+
["name", "default", "prefersdark", "root", "color-scheme", "logs"]
|
|
60
|
+
.includes(key)
|
|
54
61
|
) return;
|
|
55
62
|
|
|
56
63
|
const value = options[key];
|
|
57
64
|
|
|
58
65
|
if (keyMap[key]) {
|
|
59
|
-
// Validate color format
|
|
60
|
-
if (value && typeof value === "string") {
|
|
66
|
+
// Validate color format (only if logs enabled)
|
|
67
|
+
if (showLogs && value && typeof value === "string") {
|
|
61
68
|
// Check if value starts with oklch() or is a valid CSS color
|
|
62
69
|
const isOklch = value.trim().startsWith("oklch(");
|
|
63
70
|
const isHex = /^#[0-9A-Fa-f]{3,8}$/.test(value.trim());
|
|
@@ -74,7 +81,7 @@ export default plugin.withOptions((options = {}) => {
|
|
|
74
81
|
|
|
75
82
|
if (isHex || isRgb) {
|
|
76
83
|
console.info(
|
|
77
|
-
|
|
84
|
+
`🍀 Clampography (${themeName}): Color "${key}" uses ${
|
|
78
85
|
isHex ? "HEX" : "RGB"
|
|
79
86
|
} format. ` +
|
|
80
87
|
`Consider using OKLCH format for better color space support and smoother gradients.`,
|
|
@@ -94,15 +101,18 @@ export default plugin.withOptions((options = {}) => {
|
|
|
94
101
|
// 4. Generate Styles
|
|
95
102
|
const styles = {};
|
|
96
103
|
|
|
97
|
-
//
|
|
98
|
-
|
|
104
|
+
// Build selector based on flags
|
|
105
|
+
let selector = `[data-theme="${themeName}"]`;
|
|
99
106
|
|
|
100
|
-
//
|
|
107
|
+
// If default, prepend :where(root) with lower specificity
|
|
101
108
|
if (isDefault) {
|
|
102
|
-
|
|
109
|
+
selector = `:where(${rootSelector}),${selector}`;
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
//
|
|
112
|
+
// Apply theme to the constructed selector
|
|
113
|
+
styles[selector] = themeColors;
|
|
114
|
+
|
|
115
|
+
// If prefers-dark, apply only to root selector in media query
|
|
106
116
|
if (isPrefersDark) {
|
|
107
117
|
styles["@media (prefers-color-scheme: dark)"] = {
|
|
108
118
|
[rootSelector]: themeColors,
|