jit-colors 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.
Files changed (3) hide show
  1. package/README.md +49 -0
  2. package/jit-colors.js +235 -0
  3. package/package.json +19 -0
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # JIT Colors
2
+
3
+ A lightweight, Just-In-Time (JIT) CSS utility generator. It watches your DOM for specific class patterns and generates the corresponding CSS on the fly.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install jit-colors
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Import and start the engine in your main JavaScript entry point.
14
+
15
+ ```javascript
16
+ import jitColors from "jit-colors";
17
+
18
+ // Start the JIT engine
19
+ jitColors.start();
20
+ ```
21
+
22
+ ### Options
23
+
24
+ You can pass options to the `start` method:
25
+
26
+ ```javascript
27
+ jitColors.start({
28
+ separator: "_", // Default is '_'
29
+ });
30
+ ```
31
+
32
+ ## Features
33
+
34
+ - **Colors**: `bg-red`, `text-blue` (supports hex codes like `bg-#ff0000` or `bg-ff0000`)
35
+ - **Spacing**: `m-10px`, `p-20` (default px), `mx-auto`, `my-5%`
36
+ - **Typography**: `fs-16px`, `fw-bold`, `tw-wrap`
37
+ - **Dimensions**: `w-100%`, `h-100vh`, `minw-300px`
38
+ - **Borders**: `border-1px_solid_red`, `border-top-5px`
39
+ - **Position**: `top-0`, `left-50%`, `z-10`
40
+ - **Breakpoints**: `sm`, `md`, `lg`, `xl`, `xxl` (e.g., `md_bg-red`)
41
+
42
+ ## Example
43
+
44
+ ```html
45
+ <div class="bg-f0f0f0 p-20px rounded-8px">
46
+ <h1 class="text-333 fs-24px fw-bold">Hello World</h1>
47
+ <p class="mt-10px text-666">This is styled with JIT Colors.</p>
48
+ </div>
49
+ ```
package/jit-colors.js ADDED
@@ -0,0 +1,235 @@
1
+ export default {
2
+ start(options = {}) {
3
+ const separator = options.separator || '_';
4
+ const styleId = 'jit_styles';
5
+
6
+ // ---------------- Create <style> early ----------------
7
+ let styleTag = document.getElementById(styleId);
8
+ if (!styleTag) {
9
+ styleTag = document.createElement('style');
10
+ styleTag.id = styleId;
11
+ document.head.appendChild(styleTag);
12
+ }
13
+
14
+ const breakpoints = {
15
+ sm: '576px',
16
+ md: '768px',
17
+ lg: '992px',
18
+ xl: '1200px',
19
+ xxl: '1400px',
20
+ };
21
+ const breakpointOrder = ['base', 'sm', 'md', 'lg', 'xl', 'xxl'];
22
+
23
+ const cssBuckets = {
24
+ base: '',
25
+ sm: '',
26
+ md: '',
27
+ lg: '',
28
+ xl: '',
29
+ xxl: '',
30
+ };
31
+
32
+ const addedClasses = new Set();
33
+ const esc = (cls) => CSS.escape(cls);
34
+
35
+ const parseValue = (raw) => {
36
+ if (/^-?\d*\.?\d+[a-z%]+$/i.test(raw)) return raw;
37
+ if (/^-?\d*\.?\d+$/.test(raw)) return raw + 'px';
38
+ return null;
39
+ };
40
+
41
+ const normalizeColor = (raw) => {
42
+ if (raw.startsWith('#')) return raw;
43
+ if (/^[0-9A-Fa-f]{3,8}$/.test(raw)) return '#' + raw;
44
+ return raw;
45
+ };
46
+
47
+ const generateCSS = (className) => {
48
+ if (addedClasses.has(className)) return;
49
+
50
+ const parts = className.split(separator);
51
+ const utility = parts[0];
52
+
53
+ let bp = null;
54
+ let values = [];
55
+
56
+ if (breakpoints[parts[1]]) {
57
+ bp = parts[1];
58
+ values = parts.slice(2);
59
+ } else {
60
+ values = parts.slice(1);
61
+ }
62
+
63
+ if (!utility || !values.length) return;
64
+ const cls = esc(className);
65
+ let cssRule = '';
66
+
67
+ /* ---------- COLORS ---------- */
68
+ if (utility === 'bg' || utility === 'text') {
69
+ const colorRaw = values.join('_');
70
+ const color = normalizeColor(colorRaw);
71
+ cssRule = utility === 'bg'
72
+ ? `.${cls}{background-color:${color}!important;}`
73
+ : `.${cls}{color:${color}!important;}`;
74
+ }
75
+
76
+ /* ---------- TEXT WRAP ---------- */
77
+ else if (utility === 'tw') {
78
+ const map = {
79
+ wrap: 'white-space:normal',
80
+ nowrap: 'white-space:nowrap',
81
+ pre: 'white-space:pre',
82
+ pre_wrap: 'white-space:pre-wrap',
83
+ pre_line: 'white-space:pre-line',
84
+ break: 'word-break:break-word',
85
+ break_all: 'word-break:break-all',
86
+ keep_all: 'word-break:keep-all',
87
+ };
88
+ const key = values.join('_');
89
+ if (!map[key]) return;
90
+ cssRule = `.${cls}{${map[key]}!important;}`;
91
+ }
92
+
93
+ /* ---------- Z-INDEX ---------- */
94
+ else if (utility === 'z') {
95
+ cssRule = `.${cls}{z-index:${values[0]}!important;}`;
96
+ }
97
+
98
+ /* ---------- POSITION ---------- */
99
+ else if (['top', 'bottom', 'left', 'right'].includes(utility)) {
100
+ const value = parseValue(values[0]);
101
+ if (!value) return;
102
+ cssRule = `.${cls}{${utility}:${value}!important;}`;
103
+ }
104
+
105
+ /* ---------- BORDERS ---------- */
106
+ else if (utility === 'border') {
107
+ let pos = null, size = null, style = null, colorRaw = null;
108
+
109
+ const sideMap = {
110
+ top: 'top', bottom: 'bottom',
111
+ start: 'left', end: 'right',
112
+ left: 'left', right: 'right'
113
+ };
114
+
115
+ const isSide = (val) => Object.keys(sideMap).includes(val);
116
+
117
+ if (values.length === 2 && isSide(values[0])) {
118
+ [pos, size] = values;
119
+ } else if (values.length === 1) {
120
+ [size] = values;
121
+ } else if (values.length === 3) {
122
+ [size, style, colorRaw] = values;
123
+ } else if (values.length === 4) {
124
+ [pos, size, style, colorRaw] = values;
125
+ } else return;
126
+
127
+ const side = (pos && sideMap[pos]) ? `-${sideMap[pos]}` : '';
128
+ const sizeVal = parseValue(size);
129
+ if (!sizeVal) return;
130
+
131
+ if (parseFloat(sizeVal) === 0) {
132
+ cssRule = `.${cls}{border${side}:${sizeVal}!important;}`;
133
+ } else {
134
+ if (!style || !colorRaw) return;
135
+ cssRule = `.${cls}{border${side}:${sizeVal} ${style} ${normalizeColor(colorRaw)}!important;}`;
136
+ }
137
+ }
138
+
139
+ /* ---------- SIZE / SPACING / FONT ---------- */
140
+ else {
141
+ const value = parseValue(values[0]);
142
+ if (!value) return;
143
+
144
+ const map = {
145
+ m: 'margin', mt: 'margin-top', mb: 'margin-bottom',
146
+ ms: 'margin-left', me: 'margin-right',
147
+ p: 'padding', pt: 'padding-top', pb: 'padding-bottom',
148
+ ps: 'padding-left', pe: 'padding-right',
149
+ w: 'width', h: 'height',
150
+ minw: 'min-width', maxw: 'max-width',
151
+ minh: 'min-height', maxh: 'max-height',
152
+ fs: 'font-size', fw: 'font-weight',
153
+ rounded: 'border-radius', gap: 'gap',
154
+ };
155
+
156
+ if (utility === 'mx') {
157
+ cssRule = `.${cls}{margin-left:${value}!important;margin-right:${value}!important;}`;
158
+ }
159
+ else if (utility === 'my') {
160
+ cssRule = `.${cls}{margin-top:${value}!important;margin-bottom:${value}!important;}`;
161
+ }
162
+ else if (utility === 'px') {
163
+ cssRule = `.${cls}{padding-left:${value}!important;padding-right:${value}!important;}`;
164
+ }
165
+ else if (utility === 'py') {
166
+ cssRule = `.${cls}{padding-top:${value}!important;padding-bottom:${value}!important;}`;
167
+ }
168
+ else if (map[utility]) {
169
+ if (utility === 'fw') {
170
+ cssRule = `.${cls}{font-weight:${values[0]}!important;}`;
171
+ } else {
172
+ cssRule = `.${cls}{${map[utility]}:${value}!important;}`;
173
+ }
174
+ }
175
+ else {
176
+ return;
177
+ }
178
+ }
179
+
180
+ if (!cssRule) return;
181
+ if (bp) cssBuckets[bp] += cssRule + '\n';
182
+ else cssBuckets.base += cssRule + '\n';
183
+ addedClasses.add(className);
184
+ };
185
+
186
+ const renderStyles = () => {
187
+ let css = cssBuckets.base + '\n';
188
+ for (const bp of breakpointOrder.slice(1)) {
189
+ if (cssBuckets[bp]) {
190
+ css += `@media (min-width:${breakpoints[bp]}){\n${cssBuckets[bp]}}\n`;
191
+ }
192
+ }
193
+ styleTag.textContent = css;
194
+ };
195
+
196
+ const preload = () => {
197
+ document.querySelectorAll('*').forEach(el =>
198
+ el.classList.forEach(generateCSS)
199
+ );
200
+ renderStyles();
201
+ document.documentElement.classList.add('jit-ready');
202
+ };
203
+
204
+ preload();
205
+
206
+ const observer = new MutationObserver((mutations) => {
207
+ let changed = false;
208
+ for (const m of mutations) {
209
+ if (m.type === 'attributes' && m.attributeName === 'class') {
210
+ m.target.classList.forEach(generateCSS);
211
+ changed = true;
212
+ }
213
+ if (m.type === 'childList') {
214
+ m.addedNodes.forEach((node) => {
215
+ if (node.nodeType === 1) {
216
+ node.classList.forEach(generateCSS);
217
+ node.querySelectorAll('*').forEach(el =>
218
+ el.classList.forEach(generateCSS)
219
+ );
220
+ changed = true;
221
+ }
222
+ });
223
+ }
224
+ }
225
+ if (changed) renderStyles();
226
+ });
227
+
228
+ observer.observe(document.body, {
229
+ childList: true,
230
+ subtree: true,
231
+ attributes: true,
232
+ attributeFilter: ['class'],
233
+ });
234
+ },
235
+ };
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "jit-colors",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight JIT color and utility class generator for styling.",
5
+ "main": "jit-colors.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [
11
+ "css",
12
+ "jit",
13
+ "colors",
14
+ "utility",
15
+ "styling"
16
+ ],
17
+ "author": "",
18
+ "license": "ISC"
19
+ }