@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.
Files changed (62) hide show
  1. package/dist/builder.d.ts +5 -0
  2. package/dist/builder.d.ts.map +1 -1
  3. package/dist/builder.js +11 -1
  4. package/dist/cache/hash.d.ts +66 -0
  5. package/dist/cache/hash.d.ts.map +1 -0
  6. package/dist/cache/hash.js +161 -0
  7. package/dist/cache/index.d.ts +10 -0
  8. package/dist/cache/index.d.ts.map +1 -0
  9. package/dist/cache/index.js +12 -0
  10. package/dist/cache/lru.d.ts +122 -0
  11. package/dist/cache/lru.d.ts.map +1 -0
  12. package/dist/cache/lru.js +269 -0
  13. package/dist/cache/snapshot.d.ts +116 -0
  14. package/dist/cache/snapshot.d.ts.map +1 -0
  15. package/dist/cache/snapshot.js +204 -0
  16. package/dist/cache.d.ts +2 -2
  17. package/dist/cache.js +2 -2
  18. package/dist/css.d.ts +19 -6
  19. package/dist/css.d.ts.map +1 -1
  20. package/dist/index.d.ts +18 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +41 -10
  23. package/dist/ogx.js +2 -2
  24. package/dist/perf/index.d.ts +8 -0
  25. package/dist/perf/index.d.ts.map +1 -0
  26. package/dist/perf/index.js +7 -0
  27. package/dist/perf/timing.d.ts +160 -0
  28. package/dist/perf/timing.d.ts.map +1 -0
  29. package/dist/perf/timing.js +305 -0
  30. package/dist/presets/blog.js +1 -1
  31. package/dist/presets/docs.d.ts +2 -0
  32. package/dist/presets/docs.d.ts.map +1 -1
  33. package/dist/presets/docs.js +26 -23
  34. package/dist/presets/minimal.d.ts +2 -0
  35. package/dist/presets/minimal.d.ts.map +1 -1
  36. package/dist/presets/minimal.js +8 -16
  37. package/dist/presets/social.d.ts +2 -0
  38. package/dist/presets/social.d.ts.map +1 -1
  39. package/dist/presets/social.js +28 -18
  40. package/dist/render-png.d.ts.map +1 -1
  41. package/dist/render-png.js +9 -1
  42. package/dist/render-svg.d.ts.map +1 -1
  43. package/dist/render-svg.js +11 -1
  44. package/dist/tailwind/class-cache.d.ts +141 -0
  45. package/dist/tailwind/class-cache.d.ts.map +1 -0
  46. package/dist/tailwind/class-cache.js +212 -0
  47. package/dist/tailwind/index.d.ts +14 -1
  48. package/dist/tailwind/index.d.ts.map +1 -1
  49. package/dist/tailwind/index.js +15 -1
  50. package/dist/tailwind/lookup-tables.d.ts +30 -0
  51. package/dist/tailwind/lookup-tables.d.ts.map +1 -0
  52. package/dist/tailwind/lookup-tables.js +427 -0
  53. package/dist/tailwind/parser-v2.d.ts +54 -0
  54. package/dist/tailwind/parser-v2.d.ts.map +1 -0
  55. package/dist/tailwind/parser-v2.js +250 -0
  56. package/dist/tailwind/parser.d.ts +1 -0
  57. package/dist/tailwind/parser.d.ts.map +1 -1
  58. package/dist/tailwind/parser.js +1 -0
  59. package/dist/tailwind/prefix-handlers.d.ts +68 -0
  60. package/dist/tailwind/prefix-handlers.d.ts.map +1 -0
  61. package/dist/tailwind/prefix-handlers.js +931 -0
  62. 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
+ }