@ogxjs/core 0.1.1 → 0.2.0-alpha.1
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/builder.d.ts +5 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +11 -1
- package/dist/cache/hash.d.ts +66 -0
- package/dist/cache/hash.d.ts.map +1 -0
- package/dist/cache/hash.js +161 -0
- package/dist/cache/index.d.ts +10 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +12 -0
- package/dist/cache/lru.d.ts +122 -0
- package/dist/cache/lru.d.ts.map +1 -0
- package/dist/cache/lru.js +269 -0
- package/dist/cache/snapshot.d.ts +116 -0
- package/dist/cache/snapshot.d.ts.map +1 -0
- package/dist/cache/snapshot.js +204 -0
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +2 -2
- package/dist/css.d.ts +19 -6
- package/dist/css.d.ts.map +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -10
- package/dist/ogx.js +2 -2
- package/dist/perf/index.d.ts +8 -0
- package/dist/perf/index.d.ts.map +1 -0
- package/dist/perf/index.js +7 -0
- package/dist/perf/timing.d.ts +160 -0
- package/dist/perf/timing.d.ts.map +1 -0
- package/dist/perf/timing.js +305 -0
- package/dist/presets/blog.js +1 -1
- package/dist/presets/docs.d.ts +2 -0
- package/dist/presets/docs.d.ts.map +1 -1
- package/dist/presets/docs.js +26 -23
- package/dist/presets/minimal.d.ts +2 -0
- package/dist/presets/minimal.d.ts.map +1 -1
- package/dist/presets/minimal.js +8 -16
- package/dist/presets/social.d.ts +2 -0
- package/dist/presets/social.d.ts.map +1 -1
- package/dist/presets/social.js +28 -18
- package/dist/render-png.d.ts.map +1 -1
- package/dist/render-png.js +9 -1
- package/dist/render-svg.d.ts.map +1 -1
- package/dist/render-svg.js +11 -1
- package/dist/tailwind/class-cache.d.ts +141 -0
- package/dist/tailwind/class-cache.d.ts.map +1 -0
- package/dist/tailwind/class-cache.js +212 -0
- package/dist/tailwind/index.d.ts +14 -1
- package/dist/tailwind/index.d.ts.map +1 -1
- package/dist/tailwind/index.js +15 -1
- package/dist/tailwind/lookup-tables.d.ts +30 -0
- package/dist/tailwind/lookup-tables.d.ts.map +1 -0
- package/dist/tailwind/lookup-tables.js +427 -0
- package/dist/tailwind/parser-v2.d.ts +54 -0
- package/dist/tailwind/parser-v2.d.ts.map +1 -0
- package/dist/tailwind/parser-v2.js +250 -0
- package/dist/tailwind/parser.d.ts +1 -0
- package/dist/tailwind/parser.d.ts.map +1 -1
- package/dist/tailwind/parser.js +1 -0
- package/dist/tailwind/prefix-handlers.d.ts +68 -0
- package/dist/tailwind/prefix-handlers.d.ts.map +1 -0
- package/dist/tailwind/prefix-handlers.js +931 -0
- package/package.json +17 -2
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ogxjs/core - Tailwind Prefix Handlers
|
|
3
|
+
* Modular handlers for dynamic Tailwind classes with prefixes
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Instead of a giant if/else chain, we use a Map of prefix -> handler.
|
|
7
|
+
* This allows O(1) prefix lookup + targeted parsing.
|
|
8
|
+
*
|
|
9
|
+
* @performance
|
|
10
|
+
* - Before: O(n) linear scan through all if/else conditions
|
|
11
|
+
* - After: O(1) prefix lookup + O(1) value parsing
|
|
12
|
+
*
|
|
13
|
+
* @version 0.2.0 "Turbo"
|
|
14
|
+
*/
|
|
15
|
+
import { normalizeColor } from "../utils/color";
|
|
16
|
+
import { colors } from "./colors";
|
|
17
|
+
import { borderRadius, boxShadow, fontSize, opacity, spacing } from "./scales";
|
|
18
|
+
// UTILITY FUNCTIONS
|
|
19
|
+
/**
|
|
20
|
+
* Parse a spacing value from Tailwind scale or fraction
|
|
21
|
+
*/
|
|
22
|
+
export function parseSpacingValue(value) {
|
|
23
|
+
// Check for fraction values like 1/2, 1/3, 1/4, etc.
|
|
24
|
+
if (value.includes("/")) {
|
|
25
|
+
const [num, denom] = value.split("/");
|
|
26
|
+
const numerator = Number.parseInt(num, 10);
|
|
27
|
+
const denominator = Number.parseInt(denom, 10);
|
|
28
|
+
if (!Number.isNaN(numerator) &&
|
|
29
|
+
!Number.isNaN(denominator) &&
|
|
30
|
+
denominator !== 0) {
|
|
31
|
+
return `${(numerator / denominator) * 100}%`;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
// Check spacing scale
|
|
36
|
+
const spacingValue = spacing[value];
|
|
37
|
+
if (spacingValue !== undefined) {
|
|
38
|
+
return spacingValue;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a color value from theme or palette with opacity support
|
|
44
|
+
*/
|
|
45
|
+
export function resolveColorValue(name, theme) {
|
|
46
|
+
if (!name)
|
|
47
|
+
return undefined;
|
|
48
|
+
const normalizedParts = name.split("/");
|
|
49
|
+
const colorName = normalizedParts[0];
|
|
50
|
+
const opacityStr = normalizedParts[1];
|
|
51
|
+
if (!colorName)
|
|
52
|
+
return undefined;
|
|
53
|
+
let baseColor;
|
|
54
|
+
if (theme?.[colorName]) {
|
|
55
|
+
baseColor = theme[colorName];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
baseColor = colors[colorName];
|
|
59
|
+
}
|
|
60
|
+
if (!baseColor)
|
|
61
|
+
return undefined;
|
|
62
|
+
baseColor = normalizeColor(baseColor);
|
|
63
|
+
if (opacityStr && baseColor.startsWith("#")) {
|
|
64
|
+
const alpha = Number.parseInt(opacityStr, 10) / 100;
|
|
65
|
+
const r = Number.parseInt(baseColor.slice(1, 3), 16);
|
|
66
|
+
const g = Number.parseInt(baseColor.slice(3, 5), 16);
|
|
67
|
+
const b = Number.parseInt(baseColor.slice(5, 7), 16);
|
|
68
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
69
|
+
}
|
|
70
|
+
return baseColor;
|
|
71
|
+
}
|
|
72
|
+
/** Maximum length for arbitrary values to prevent DoS */
|
|
73
|
+
const MAX_ARBITRARY_VALUE_LENGTH = 100;
|
|
74
|
+
/** Patterns that could indicate malicious input */
|
|
75
|
+
const DANGEROUS_PATTERNS = /javascript:|data:text\/html|<script|<\/script|expression\s*\(/i;
|
|
76
|
+
/**
|
|
77
|
+
* Parse arbitrary value from brackets [value]
|
|
78
|
+
*/
|
|
79
|
+
export function parseArbitraryValue(value) {
|
|
80
|
+
// Security: Limit value length
|
|
81
|
+
if (value.length > MAX_ARBITRARY_VALUE_LENGTH) {
|
|
82
|
+
if (process.env.NODE_ENV !== "production") {
|
|
83
|
+
console.warn(`OGX: Arbitrary value exceeds ${MAX_ARBITRARY_VALUE_LENGTH} chars, truncating`);
|
|
84
|
+
}
|
|
85
|
+
value = value.slice(0, MAX_ARBITRARY_VALUE_LENGTH);
|
|
86
|
+
}
|
|
87
|
+
// Security: Block dangerous patterns
|
|
88
|
+
if (DANGEROUS_PATTERNS.test(value)) {
|
|
89
|
+
if (process.env.NODE_ENV !== "production") {
|
|
90
|
+
console.warn(`OGX: Potentially dangerous arbitrary value blocked`);
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
// CSS value with units
|
|
95
|
+
if (/^\d+(\.\d+)?(px|rem|em|%|vw|vh)$/.test(value)) {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
// Pure number
|
|
99
|
+
if (/^\d+(\.\d+)?$/.test(value)) {
|
|
100
|
+
return Number.parseFloat(value);
|
|
101
|
+
}
|
|
102
|
+
// Return as-is (colors, etc.)
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
// PREFIX HANDLERS
|
|
106
|
+
/**
|
|
107
|
+
* Map of prefix -> handler function
|
|
108
|
+
* Order matters for prefixes that share common starts (e.g., "px-" before "p-")
|
|
109
|
+
*/
|
|
110
|
+
export const PREFIX_HANDLERS = new Map([
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
112
|
+
// PADDING (order: px/py before p)
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
114
|
+
[
|
|
115
|
+
"px-",
|
|
116
|
+
(value, ctx) => {
|
|
117
|
+
const v = parseSpacingValue(value);
|
|
118
|
+
if (v !== undefined) {
|
|
119
|
+
ctx.style.paddingLeft = v;
|
|
120
|
+
ctx.style.paddingRight = v;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
"py-",
|
|
126
|
+
(value, ctx) => {
|
|
127
|
+
const v = parseSpacingValue(value);
|
|
128
|
+
if (v !== undefined) {
|
|
129
|
+
ctx.style.paddingTop = v;
|
|
130
|
+
ctx.style.paddingBottom = v;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"pt-",
|
|
136
|
+
(value, ctx) => {
|
|
137
|
+
const v = parseSpacingValue(value);
|
|
138
|
+
if (v !== undefined)
|
|
139
|
+
ctx.style.paddingTop = v;
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
"pr-",
|
|
144
|
+
(value, ctx) => {
|
|
145
|
+
const v = parseSpacingValue(value);
|
|
146
|
+
if (v !== undefined)
|
|
147
|
+
ctx.style.paddingRight = v;
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"pb-",
|
|
152
|
+
(value, ctx) => {
|
|
153
|
+
const v = parseSpacingValue(value);
|
|
154
|
+
if (v !== undefined)
|
|
155
|
+
ctx.style.paddingBottom = v;
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
"pl-",
|
|
160
|
+
(value, ctx) => {
|
|
161
|
+
const v = parseSpacingValue(value);
|
|
162
|
+
if (v !== undefined)
|
|
163
|
+
ctx.style.paddingLeft = v;
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
"p-",
|
|
168
|
+
(value, ctx) => {
|
|
169
|
+
const v = parseSpacingValue(value);
|
|
170
|
+
if (v !== undefined)
|
|
171
|
+
ctx.style.padding = v;
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
175
|
+
// MARGIN (order: mx/my before m)
|
|
176
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
177
|
+
[
|
|
178
|
+
"mx-",
|
|
179
|
+
(value, ctx) => {
|
|
180
|
+
const v = parseSpacingValue(value);
|
|
181
|
+
if (v !== undefined) {
|
|
182
|
+
ctx.style.marginLeft = v;
|
|
183
|
+
ctx.style.marginRight = v;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
[
|
|
188
|
+
"my-",
|
|
189
|
+
(value, ctx) => {
|
|
190
|
+
const v = parseSpacingValue(value);
|
|
191
|
+
if (v !== undefined) {
|
|
192
|
+
ctx.style.marginTop = v;
|
|
193
|
+
ctx.style.marginBottom = v;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
"mt-",
|
|
199
|
+
(value, ctx) => {
|
|
200
|
+
const v = parseSpacingValue(value);
|
|
201
|
+
if (v !== undefined)
|
|
202
|
+
ctx.style.marginTop = v;
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
"mr-",
|
|
207
|
+
(value, ctx) => {
|
|
208
|
+
const v = parseSpacingValue(value);
|
|
209
|
+
if (v !== undefined)
|
|
210
|
+
ctx.style.marginRight = v;
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
"mb-",
|
|
215
|
+
(value, ctx) => {
|
|
216
|
+
const v = parseSpacingValue(value);
|
|
217
|
+
if (v !== undefined)
|
|
218
|
+
ctx.style.marginBottom = v;
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"ml-",
|
|
223
|
+
(value, ctx) => {
|
|
224
|
+
const v = parseSpacingValue(value);
|
|
225
|
+
if (v !== undefined)
|
|
226
|
+
ctx.style.marginLeft = v;
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
[
|
|
230
|
+
"m-",
|
|
231
|
+
(value, ctx) => {
|
|
232
|
+
const v = parseSpacingValue(value);
|
|
233
|
+
if (v !== undefined)
|
|
234
|
+
ctx.style.margin = v;
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
238
|
+
// GAP (order: gap-x/gap-y before gap)
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
240
|
+
[
|
|
241
|
+
"gap-x-",
|
|
242
|
+
(value, ctx) => {
|
|
243
|
+
const v = parseSpacingValue(value);
|
|
244
|
+
if (v !== undefined)
|
|
245
|
+
ctx.style.columnGap = v;
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
[
|
|
249
|
+
"gap-y-",
|
|
250
|
+
(value, ctx) => {
|
|
251
|
+
const v = parseSpacingValue(value);
|
|
252
|
+
if (v !== undefined)
|
|
253
|
+
ctx.style.rowGap = v;
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
[
|
|
257
|
+
"gap-",
|
|
258
|
+
(value, ctx) => {
|
|
259
|
+
const v = parseSpacingValue(value);
|
|
260
|
+
if (v !== undefined)
|
|
261
|
+
ctx.style.gap = v;
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
265
|
+
// WIDTH / HEIGHT
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
267
|
+
[
|
|
268
|
+
"w-",
|
|
269
|
+
(value, ctx) => {
|
|
270
|
+
const v = parseSpacingValue(value);
|
|
271
|
+
if (v !== undefined)
|
|
272
|
+
ctx.style.width = v;
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
[
|
|
276
|
+
"h-",
|
|
277
|
+
(value, ctx) => {
|
|
278
|
+
const v = parseSpacingValue(value);
|
|
279
|
+
if (v !== undefined)
|
|
280
|
+
ctx.style.height = v;
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
[
|
|
284
|
+
"min-w-",
|
|
285
|
+
(value, ctx) => {
|
|
286
|
+
const v = parseSpacingValue(value);
|
|
287
|
+
if (v !== undefined)
|
|
288
|
+
ctx.style.minWidth = v;
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
[
|
|
292
|
+
"min-h-",
|
|
293
|
+
(value, ctx) => {
|
|
294
|
+
const v = parseSpacingValue(value);
|
|
295
|
+
if (v !== undefined)
|
|
296
|
+
ctx.style.minHeight = v;
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
[
|
|
300
|
+
"max-w-",
|
|
301
|
+
(value, ctx) => {
|
|
302
|
+
const v = parseSpacingValue(value);
|
|
303
|
+
if (v !== undefined)
|
|
304
|
+
ctx.style.maxWidth = v;
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
[
|
|
308
|
+
"max-h-",
|
|
309
|
+
(value, ctx) => {
|
|
310
|
+
const v = parseSpacingValue(value);
|
|
311
|
+
if (v !== undefined)
|
|
312
|
+
ctx.style.maxHeight = v;
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// INSET / POSITION
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
318
|
+
[
|
|
319
|
+
"inset-x-",
|
|
320
|
+
(value, ctx) => {
|
|
321
|
+
const v = parseSpacingValue(value);
|
|
322
|
+
if (v !== undefined) {
|
|
323
|
+
ctx.style.left = v;
|
|
324
|
+
ctx.style.right = v;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
"inset-y-",
|
|
330
|
+
(value, ctx) => {
|
|
331
|
+
const v = parseSpacingValue(value);
|
|
332
|
+
if (v !== undefined) {
|
|
333
|
+
ctx.style.top = v;
|
|
334
|
+
ctx.style.bottom = v;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
[
|
|
339
|
+
"inset-",
|
|
340
|
+
(value, ctx) => {
|
|
341
|
+
const v = parseSpacingValue(value);
|
|
342
|
+
if (v !== undefined) {
|
|
343
|
+
ctx.style.top = v;
|
|
344
|
+
ctx.style.right = v;
|
|
345
|
+
ctx.style.bottom = v;
|
|
346
|
+
ctx.style.left = v;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
[
|
|
351
|
+
"top-",
|
|
352
|
+
(value, ctx) => {
|
|
353
|
+
const v = parseSpacingValue(value);
|
|
354
|
+
if (v !== undefined)
|
|
355
|
+
ctx.style.top = v;
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
[
|
|
359
|
+
"right-",
|
|
360
|
+
(value, ctx) => {
|
|
361
|
+
const v = parseSpacingValue(value);
|
|
362
|
+
if (v !== undefined)
|
|
363
|
+
ctx.style.right = v;
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
[
|
|
367
|
+
"bottom-",
|
|
368
|
+
(value, ctx) => {
|
|
369
|
+
const v = parseSpacingValue(value);
|
|
370
|
+
if (v !== undefined)
|
|
371
|
+
ctx.style.bottom = v;
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
[
|
|
375
|
+
"left-",
|
|
376
|
+
(value, ctx) => {
|
|
377
|
+
const v = parseSpacingValue(value);
|
|
378
|
+
if (v !== undefined)
|
|
379
|
+
ctx.style.left = v;
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
383
|
+
// Z-INDEX
|
|
384
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
385
|
+
[
|
|
386
|
+
"z-",
|
|
387
|
+
(value, ctx) => {
|
|
388
|
+
const num = Number.parseInt(value, 10);
|
|
389
|
+
if (!Number.isNaN(num))
|
|
390
|
+
ctx.style.zIndex = num;
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
394
|
+
// BACKGROUND
|
|
395
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
396
|
+
[
|
|
397
|
+
"bg-gradient-to-",
|
|
398
|
+
(value, ctx) => {
|
|
399
|
+
const directions = {
|
|
400
|
+
t: "to top",
|
|
401
|
+
tr: "to top right",
|
|
402
|
+
r: "to right",
|
|
403
|
+
br: "to bottom right",
|
|
404
|
+
b: "to bottom",
|
|
405
|
+
bl: "to bottom left",
|
|
406
|
+
l: "to left",
|
|
407
|
+
tl: "to top left",
|
|
408
|
+
};
|
|
409
|
+
if (directions[value]) {
|
|
410
|
+
ctx.gradient.direction = directions[value];
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
[
|
|
415
|
+
"bg-",
|
|
416
|
+
(value, ctx) => {
|
|
417
|
+
// Skip if it's a gradient direction
|
|
418
|
+
if (value.startsWith("gradient-"))
|
|
419
|
+
return;
|
|
420
|
+
// Handle grid/dots/lines patterns
|
|
421
|
+
if (value.startsWith("grid") ||
|
|
422
|
+
value.startsWith("dots") ||
|
|
423
|
+
value.startsWith("lines")) {
|
|
424
|
+
handleBackgroundPattern(value, ctx);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
// Handle grain
|
|
428
|
+
if (value.startsWith("grain")) {
|
|
429
|
+
handleBackgroundGrain(value, ctx);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// Regular color
|
|
433
|
+
const resolved = resolveColorValue(value, ctx.theme);
|
|
434
|
+
if (resolved) {
|
|
435
|
+
ctx.style.backgroundColor = resolved;
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
440
|
+
// GRADIENT COLORS
|
|
441
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
442
|
+
[
|
|
443
|
+
"from-",
|
|
444
|
+
(value, ctx) => {
|
|
445
|
+
const color = resolveColorValue(value, ctx.theme);
|
|
446
|
+
if (color)
|
|
447
|
+
ctx.gradient.from = color;
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
[
|
|
451
|
+
"via-",
|
|
452
|
+
(value, ctx) => {
|
|
453
|
+
const color = resolveColorValue(value, ctx.theme);
|
|
454
|
+
if (color)
|
|
455
|
+
ctx.gradient.via = color;
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
[
|
|
459
|
+
"to-",
|
|
460
|
+
(value, ctx) => {
|
|
461
|
+
const color = resolveColorValue(value, ctx.theme);
|
|
462
|
+
if (color)
|
|
463
|
+
ctx.gradient.to = color;
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
467
|
+
// TEXT
|
|
468
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
469
|
+
[
|
|
470
|
+
"text-",
|
|
471
|
+
(value, ctx) => {
|
|
472
|
+
// Check if it's a font size first
|
|
473
|
+
const size = fontSize[value];
|
|
474
|
+
if (size) {
|
|
475
|
+
ctx.style.fontSize = `${size[0]}px`;
|
|
476
|
+
ctx.style.lineHeight = `${size[1]}px`;
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
// Check alignment
|
|
480
|
+
if (value === "left" ||
|
|
481
|
+
value === "center" ||
|
|
482
|
+
value === "right" ||
|
|
483
|
+
value === "justify") {
|
|
484
|
+
ctx.style.textAlign = value;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// Otherwise it's a color
|
|
488
|
+
const resolved = resolveColorValue(value, ctx.theme);
|
|
489
|
+
if (resolved) {
|
|
490
|
+
ctx.style.color = resolved;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
495
|
+
// FONT
|
|
496
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
497
|
+
[
|
|
498
|
+
"font-",
|
|
499
|
+
(value, ctx) => {
|
|
500
|
+
// Font weight is handled in static classes
|
|
501
|
+
// This handles font-family if needed
|
|
502
|
+
ctx.style.fontFamily = value;
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
506
|
+
// LINE HEIGHT
|
|
507
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
508
|
+
[
|
|
509
|
+
"leading-",
|
|
510
|
+
(value, ctx) => {
|
|
511
|
+
const spacingValue = spacing[value];
|
|
512
|
+
if (spacingValue !== undefined) {
|
|
513
|
+
ctx.style.lineHeight = spacingValue;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
// Named values handled in static classes
|
|
517
|
+
const num = Number.parseFloat(value);
|
|
518
|
+
if (!Number.isNaN(num)) {
|
|
519
|
+
ctx.style.lineHeight = num;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
525
|
+
// LETTER SPACING
|
|
526
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
527
|
+
[
|
|
528
|
+
"tracking-",
|
|
529
|
+
(value, ctx) => {
|
|
530
|
+
// Named values handled in static classes
|
|
531
|
+
// This handles arbitrary values like tracking-[0.5em]
|
|
532
|
+
ctx.style.letterSpacing = value;
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
536
|
+
// BORDER
|
|
537
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
538
|
+
[
|
|
539
|
+
"border-",
|
|
540
|
+
(value, ctx) => {
|
|
541
|
+
// Check if it's a width
|
|
542
|
+
const width = Number.parseInt(value, 10);
|
|
543
|
+
if (!Number.isNaN(width) && value === String(width)) {
|
|
544
|
+
ctx.style.borderWidth = width;
|
|
545
|
+
ctx.style.borderStyle = "solid";
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
// Otherwise it's a color
|
|
549
|
+
const resolved = resolveColorValue(value, ctx.theme);
|
|
550
|
+
if (resolved) {
|
|
551
|
+
ctx.style.borderColor = resolved;
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
556
|
+
// BORDER RADIUS
|
|
557
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
558
|
+
[
|
|
559
|
+
"rounded-",
|
|
560
|
+
(value, ctx) => {
|
|
561
|
+
// Named values handled in static classes
|
|
562
|
+
const radius = borderRadius[value];
|
|
563
|
+
if (radius !== undefined) {
|
|
564
|
+
ctx.style.borderRadius = radius;
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
569
|
+
// SHADOW
|
|
570
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
571
|
+
[
|
|
572
|
+
"shadow-",
|
|
573
|
+
(value, ctx) => {
|
|
574
|
+
// Named values handled in static classes
|
|
575
|
+
const shadow = boxShadow[value];
|
|
576
|
+
if (shadow !== undefined) {
|
|
577
|
+
ctx.style.boxShadow = shadow;
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
582
|
+
// OPACITY
|
|
583
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
584
|
+
[
|
|
585
|
+
"opacity-",
|
|
586
|
+
(value, ctx) => {
|
|
587
|
+
const op = opacity[value];
|
|
588
|
+
if (op !== undefined) {
|
|
589
|
+
ctx.style.opacity = op;
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
594
|
+
// ASPECT RATIO
|
|
595
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
596
|
+
[
|
|
597
|
+
"aspect-",
|
|
598
|
+
(value, ctx) => {
|
|
599
|
+
// Named values handled in static classes
|
|
600
|
+
// This handles arbitrary values like aspect-[4/3]
|
|
601
|
+
if (value.includes("/")) {
|
|
602
|
+
ctx.style.aspectRatio = value.replace("/", " / ");
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
]);
|
|
607
|
+
// SPECIAL HANDLERS
|
|
608
|
+
/**
|
|
609
|
+
* Handle background patterns (grid, dots, lines)
|
|
610
|
+
*/
|
|
611
|
+
function handleBackgroundPattern(value, ctx) {
|
|
612
|
+
const isGrid = value.startsWith("grid");
|
|
613
|
+
const isDots = value.startsWith("dots");
|
|
614
|
+
const type = isGrid ? "grid" : isDots ? "dots" : "lines";
|
|
615
|
+
// Extract config after type
|
|
616
|
+
const config = value.slice(type.length + 1) || ""; // +1 for the '-' or empty
|
|
617
|
+
let color = "rgba(128, 128, 128, 0.1)";
|
|
618
|
+
let size = 24;
|
|
619
|
+
if (config) {
|
|
620
|
+
const parts = config.split("-");
|
|
621
|
+
const lastPart = parts[parts.length - 1];
|
|
622
|
+
const isNumeric = lastPart && /^\d+$/.test(lastPart);
|
|
623
|
+
if (isNumeric) {
|
|
624
|
+
if (parts.length === 1) {
|
|
625
|
+
size = Number.parseInt(lastPart, 10);
|
|
626
|
+
}
|
|
627
|
+
else if (parts.length >= 2) {
|
|
628
|
+
size = Number.parseInt(lastPart, 10);
|
|
629
|
+
const colorName = parts.slice(0, -1).join("-");
|
|
630
|
+
if (colorName) {
|
|
631
|
+
const resolved = resolveColorValue(colorName, ctx.theme);
|
|
632
|
+
if (resolved)
|
|
633
|
+
color = resolved;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
const resolved = resolveColorValue(config, ctx.theme);
|
|
639
|
+
if (resolved)
|
|
640
|
+
color = resolved;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
ctx.style.backgroundSize = `${size}px ${size}px`;
|
|
644
|
+
if (type === "grid") {
|
|
645
|
+
ctx.style.backgroundImage = `linear-gradient(to right, ${color} 1px, transparent 1px), linear-gradient(to bottom, ${color} 1px, transparent 1px)`;
|
|
646
|
+
}
|
|
647
|
+
else if (type === "dots") {
|
|
648
|
+
ctx.style.backgroundImage = `radial-gradient(${color} 15%, transparent 16%)`;
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
ctx.style.backgroundImage = `linear-gradient(to bottom, ${color} 1px, transparent 1px)`;
|
|
652
|
+
ctx.style.backgroundSize = `100% ${size}px`;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Handle background grain (noise) effect
|
|
657
|
+
*/
|
|
658
|
+
function handleBackgroundGrain(value, ctx) {
|
|
659
|
+
const opacityValue = value.includes("/")
|
|
660
|
+
? Number.parseInt(value.split("/")[1], 10) / 100
|
|
661
|
+
: 0.05;
|
|
662
|
+
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.6' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#n)' opacity='${opacityValue}'/></svg>`;
|
|
663
|
+
// Use btoa for cross-platform compatibility (Node.js, Edge, Browser)
|
|
664
|
+
const base64 = typeof Buffer !== "undefined"
|
|
665
|
+
? Buffer.from(svg).toString("base64")
|
|
666
|
+
: btoa(svg);
|
|
667
|
+
const noiseUrl = `url(data:image/svg+xml;base64,${base64})`;
|
|
668
|
+
ctx.style.backgroundImage = ctx.style.backgroundImage
|
|
669
|
+
? `${ctx.style.backgroundImage}, ${noiseUrl}`
|
|
670
|
+
: noiseUrl;
|
|
671
|
+
}
|
|
672
|
+
// ARBITRARY VALUE HANDLER
|
|
673
|
+
/**
|
|
674
|
+
* Handle arbitrary values like p-[32px], bg-[#ff0000]
|
|
675
|
+
*/
|
|
676
|
+
export function handleArbitraryValue(prefix, value, ctx) {
|
|
677
|
+
const parsedValue = parseArbitraryValue(value);
|
|
678
|
+
switch (prefix) {
|
|
679
|
+
// Gradient
|
|
680
|
+
case "from":
|
|
681
|
+
ctx.gradient.from = String(parsedValue);
|
|
682
|
+
break;
|
|
683
|
+
case "via":
|
|
684
|
+
ctx.gradient.via = String(parsedValue);
|
|
685
|
+
break;
|
|
686
|
+
case "to":
|
|
687
|
+
ctx.gradient.to = String(parsedValue);
|
|
688
|
+
break;
|
|
689
|
+
// Padding
|
|
690
|
+
case "p":
|
|
691
|
+
ctx.style.padding = parsedValue;
|
|
692
|
+
break;
|
|
693
|
+
case "px":
|
|
694
|
+
ctx.style.paddingLeft = parsedValue;
|
|
695
|
+
ctx.style.paddingRight = parsedValue;
|
|
696
|
+
break;
|
|
697
|
+
case "py":
|
|
698
|
+
ctx.style.paddingTop = parsedValue;
|
|
699
|
+
ctx.style.paddingBottom = parsedValue;
|
|
700
|
+
break;
|
|
701
|
+
case "pt":
|
|
702
|
+
ctx.style.paddingTop = parsedValue;
|
|
703
|
+
break;
|
|
704
|
+
case "pr":
|
|
705
|
+
ctx.style.paddingRight = parsedValue;
|
|
706
|
+
break;
|
|
707
|
+
case "pb":
|
|
708
|
+
ctx.style.paddingBottom = parsedValue;
|
|
709
|
+
break;
|
|
710
|
+
case "pl":
|
|
711
|
+
ctx.style.paddingLeft = parsedValue;
|
|
712
|
+
break;
|
|
713
|
+
// Margin
|
|
714
|
+
case "m":
|
|
715
|
+
ctx.style.margin = parsedValue;
|
|
716
|
+
break;
|
|
717
|
+
case "mx":
|
|
718
|
+
ctx.style.marginLeft = parsedValue;
|
|
719
|
+
ctx.style.marginRight = parsedValue;
|
|
720
|
+
break;
|
|
721
|
+
case "my":
|
|
722
|
+
ctx.style.marginTop = parsedValue;
|
|
723
|
+
ctx.style.marginBottom = parsedValue;
|
|
724
|
+
break;
|
|
725
|
+
case "mt":
|
|
726
|
+
ctx.style.marginTop = parsedValue;
|
|
727
|
+
break;
|
|
728
|
+
case "mr":
|
|
729
|
+
ctx.style.marginRight = parsedValue;
|
|
730
|
+
break;
|
|
731
|
+
case "mb":
|
|
732
|
+
ctx.style.marginBottom = parsedValue;
|
|
733
|
+
break;
|
|
734
|
+
case "ml":
|
|
735
|
+
ctx.style.marginLeft = parsedValue;
|
|
736
|
+
break;
|
|
737
|
+
// Size
|
|
738
|
+
case "w":
|
|
739
|
+
ctx.style.width = parsedValue;
|
|
740
|
+
break;
|
|
741
|
+
case "h":
|
|
742
|
+
ctx.style.height = parsedValue;
|
|
743
|
+
break;
|
|
744
|
+
case "min-w":
|
|
745
|
+
ctx.style.minWidth = parsedValue;
|
|
746
|
+
break;
|
|
747
|
+
case "min-h":
|
|
748
|
+
ctx.style.minHeight = parsedValue;
|
|
749
|
+
break;
|
|
750
|
+
case "max-w":
|
|
751
|
+
ctx.style.maxWidth = parsedValue;
|
|
752
|
+
break;
|
|
753
|
+
case "max-h":
|
|
754
|
+
ctx.style.maxHeight = parsedValue;
|
|
755
|
+
break;
|
|
756
|
+
// Gap
|
|
757
|
+
case "gap":
|
|
758
|
+
ctx.style.gap = parsedValue;
|
|
759
|
+
break;
|
|
760
|
+
case "gap-x":
|
|
761
|
+
ctx.style.columnGap = parsedValue;
|
|
762
|
+
break;
|
|
763
|
+
case "gap-y":
|
|
764
|
+
ctx.style.rowGap = parsedValue;
|
|
765
|
+
break;
|
|
766
|
+
// Position
|
|
767
|
+
case "top":
|
|
768
|
+
ctx.style.top = parsedValue;
|
|
769
|
+
break;
|
|
770
|
+
case "right":
|
|
771
|
+
ctx.style.right = parsedValue;
|
|
772
|
+
break;
|
|
773
|
+
case "bottom":
|
|
774
|
+
ctx.style.bottom = parsedValue;
|
|
775
|
+
break;
|
|
776
|
+
case "left":
|
|
777
|
+
ctx.style.left = parsedValue;
|
|
778
|
+
break;
|
|
779
|
+
case "inset":
|
|
780
|
+
ctx.style.top = parsedValue;
|
|
781
|
+
ctx.style.right = parsedValue;
|
|
782
|
+
ctx.style.bottom = parsedValue;
|
|
783
|
+
ctx.style.left = parsedValue;
|
|
784
|
+
break;
|
|
785
|
+
// Colors
|
|
786
|
+
case "bg":
|
|
787
|
+
ctx.style.backgroundColor = normalizeColor(String(parsedValue));
|
|
788
|
+
break;
|
|
789
|
+
case "text":
|
|
790
|
+
// Could be color or size
|
|
791
|
+
if (String(parsedValue).includes("px") ||
|
|
792
|
+
String(parsedValue).includes("rem")) {
|
|
793
|
+
ctx.style.fontSize = parsedValue;
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
ctx.style.color = normalizeColor(String(parsedValue));
|
|
797
|
+
}
|
|
798
|
+
break;
|
|
799
|
+
// Border
|
|
800
|
+
case "border":
|
|
801
|
+
if (typeof parsedValue === "string" &&
|
|
802
|
+
(parsedValue.startsWith("#") ||
|
|
803
|
+
parsedValue.startsWith("rgb") ||
|
|
804
|
+
parsedValue.startsWith("hsl") ||
|
|
805
|
+
parsedValue.startsWith("oklch"))) {
|
|
806
|
+
ctx.style.borderColor = normalizeColor(parsedValue);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
ctx.style.borderWidth = parsedValue;
|
|
810
|
+
}
|
|
811
|
+
ctx.style.borderStyle = "solid";
|
|
812
|
+
break;
|
|
813
|
+
// Misc
|
|
814
|
+
case "rounded":
|
|
815
|
+
ctx.style.borderRadius = parsedValue;
|
|
816
|
+
break;
|
|
817
|
+
case "opacity":
|
|
818
|
+
ctx.style.opacity = Number.parseFloat(String(parsedValue)) / 100;
|
|
819
|
+
break;
|
|
820
|
+
case "z":
|
|
821
|
+
ctx.style.zIndex = parsedValue;
|
|
822
|
+
break;
|
|
823
|
+
case "aspect":
|
|
824
|
+
ctx.style.aspectRatio = String(parsedValue).replace("/", " / ");
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// PREFIX MATCHING
|
|
829
|
+
/**
|
|
830
|
+
* Ordered list of prefixes for matching (longest first to avoid false matches)
|
|
831
|
+
*/
|
|
832
|
+
export const ORDERED_PREFIXES = [
|
|
833
|
+
// Long prefixes first
|
|
834
|
+
"bg-gradient-to-",
|
|
835
|
+
"gap-x-",
|
|
836
|
+
"gap-y-",
|
|
837
|
+
"inset-x-",
|
|
838
|
+
"inset-y-",
|
|
839
|
+
"min-w-",
|
|
840
|
+
"min-h-",
|
|
841
|
+
"max-w-",
|
|
842
|
+
"max-h-",
|
|
843
|
+
// Medium prefixes
|
|
844
|
+
"rounded-",
|
|
845
|
+
"shadow-",
|
|
846
|
+
"border-",
|
|
847
|
+
"leading-",
|
|
848
|
+
"tracking-",
|
|
849
|
+
"opacity-",
|
|
850
|
+
"aspect-",
|
|
851
|
+
"inset-",
|
|
852
|
+
"from-",
|
|
853
|
+
"via-",
|
|
854
|
+
"to-",
|
|
855
|
+
"bg-",
|
|
856
|
+
// Short prefixes (order matters)
|
|
857
|
+
"px-",
|
|
858
|
+
"py-",
|
|
859
|
+
"pt-",
|
|
860
|
+
"pr-",
|
|
861
|
+
"pb-",
|
|
862
|
+
"pl-",
|
|
863
|
+
"mx-",
|
|
864
|
+
"my-",
|
|
865
|
+
"mt-",
|
|
866
|
+
"mr-",
|
|
867
|
+
"mb-",
|
|
868
|
+
"ml-",
|
|
869
|
+
"gap-",
|
|
870
|
+
"text-",
|
|
871
|
+
"font-",
|
|
872
|
+
"top-",
|
|
873
|
+
"right-",
|
|
874
|
+
"bottom-",
|
|
875
|
+
"left-",
|
|
876
|
+
"w-",
|
|
877
|
+
"h-",
|
|
878
|
+
"p-",
|
|
879
|
+
"m-",
|
|
880
|
+
"z-",
|
|
881
|
+
];
|
|
882
|
+
/**
|
|
883
|
+
* Index prefixes by first character for O(1) initial lookup
|
|
884
|
+
* Each character maps to array of prefixes starting with that char
|
|
885
|
+
* Prefixes are pre-sorted by length (longest first)
|
|
886
|
+
*/
|
|
887
|
+
const PREFIX_INDEX = new Map();
|
|
888
|
+
// Build index at module load (one-time cost)
|
|
889
|
+
(() => {
|
|
890
|
+
const byChar = new Map();
|
|
891
|
+
for (const prefix of ORDERED_PREFIXES) {
|
|
892
|
+
const char = prefix[0];
|
|
893
|
+
if (!byChar.has(char)) {
|
|
894
|
+
byChar.set(char, []);
|
|
895
|
+
}
|
|
896
|
+
byChar.get(char).push(prefix);
|
|
897
|
+
}
|
|
898
|
+
// Ensure each char's prefixes are sorted by length (longest first)
|
|
899
|
+
for (const [char, prefixes] of byChar) {
|
|
900
|
+
prefixes.sort((a, b) => b.length - a.length);
|
|
901
|
+
PREFIX_INDEX.set(char, Object.freeze(prefixes));
|
|
902
|
+
}
|
|
903
|
+
})();
|
|
904
|
+
/**
|
|
905
|
+
* Find matching prefix handler for a class
|
|
906
|
+
* @performance O(k) where k = prefixes starting with same char (usually 2-4)
|
|
907
|
+
*
|
|
908
|
+
* @param cls - Tailwind class name
|
|
909
|
+
* @returns [handler, value] tuple or undefined
|
|
910
|
+
*/
|
|
911
|
+
export function findPrefixHandler(cls) {
|
|
912
|
+
// Get prefixes for first character
|
|
913
|
+
const firstChar = cls[0];
|
|
914
|
+
if (!firstChar)
|
|
915
|
+
return undefined;
|
|
916
|
+
const candidates = PREFIX_INDEX.get(firstChar);
|
|
917
|
+
if (!candidates)
|
|
918
|
+
return undefined;
|
|
919
|
+
// Check candidates (already sorted by length)
|
|
920
|
+
const len = candidates.length;
|
|
921
|
+
for (let i = 0; i < len; i++) {
|
|
922
|
+
const prefix = candidates[i];
|
|
923
|
+
if (cls.startsWith(prefix)) {
|
|
924
|
+
const handler = PREFIX_HANDLERS.get(prefix);
|
|
925
|
+
if (handler) {
|
|
926
|
+
return [handler, cls.slice(prefix.length)];
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|