@cwcss/crosswind 0.1.5 → 0.1.6

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 (86) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +390 -0
  3. package/dist/build.d.ts +24 -0
  4. package/dist/config.d.ts +5 -0
  5. package/dist/generator.d.ts +31 -0
  6. package/dist/index.d.ts +10 -0
  7. package/dist/index.js +12798 -0
  8. package/dist/parser.d.ts +42 -0
  9. package/dist/plugin.d.ts +22 -0
  10. package/dist/preflight-forms.d.ts +5 -0
  11. package/dist/preflight.d.ts +2 -0
  12. package/dist/rules-advanced.d.ts +27 -0
  13. package/dist/rules-effects.d.ts +25 -0
  14. package/dist/rules-forms.d.ts +7 -0
  15. package/dist/rules-grid.d.ts +13 -0
  16. package/dist/rules-interactivity.d.ts +41 -0
  17. package/dist/rules-layout.d.ts +26 -0
  18. package/dist/rules-transforms.d.ts +33 -0
  19. package/dist/rules-typography.d.ts +41 -0
  20. package/dist/rules.d.ts +39 -0
  21. package/dist/scanner.d.ts +18 -0
  22. package/dist/transformer-compile-class.d.ts +37 -0
  23. package/{src/types.ts → dist/types.d.ts} +17 -86
  24. package/package.json +1 -1
  25. package/PLUGIN.md +0 -235
  26. package/benchmark/framework-comparison.bench.ts +0 -850
  27. package/bin/cli.ts +0 -365
  28. package/bin/crosswind +0 -0
  29. package/bin/headwind +0 -0
  30. package/build.ts +0 -8
  31. package/crosswind.config.ts +0 -9
  32. package/example/comprehensive.html +0 -70
  33. package/example/index.html +0 -21
  34. package/example/output.css +0 -236
  35. package/examples/plugin/README.md +0 -112
  36. package/examples/plugin/build.ts +0 -32
  37. package/examples/plugin/src/index.html +0 -34
  38. package/examples/plugin/src/index.ts +0 -7
  39. package/headwind +0 -2
  40. package/src/build.ts +0 -101
  41. package/src/config.ts +0 -529
  42. package/src/generator.ts +0 -2173
  43. package/src/index.ts +0 -10
  44. package/src/parser.ts +0 -1471
  45. package/src/plugin.ts +0 -118
  46. package/src/preflight-forms.ts +0 -229
  47. package/src/preflight.ts +0 -388
  48. package/src/rules-advanced.ts +0 -477
  49. package/src/rules-effects.ts +0 -461
  50. package/src/rules-forms.ts +0 -103
  51. package/src/rules-grid.ts +0 -241
  52. package/src/rules-interactivity.ts +0 -525
  53. package/src/rules-layout.ts +0 -385
  54. package/src/rules-transforms.ts +0 -412
  55. package/src/rules-typography.ts +0 -486
  56. package/src/rules.ts +0 -809
  57. package/src/scanner.ts +0 -84
  58. package/src/transformer-compile-class.ts +0 -275
  59. package/test/advanced-features.test.ts +0 -911
  60. package/test/arbitrary.test.ts +0 -396
  61. package/test/attributify.test.ts +0 -592
  62. package/test/bracket-syntax.test.ts +0 -1133
  63. package/test/build.test.ts +0 -99
  64. package/test/colors.test.ts +0 -934
  65. package/test/flexbox.test.ts +0 -669
  66. package/test/generator.test.ts +0 -597
  67. package/test/grid.test.ts +0 -584
  68. package/test/layout.test.ts +0 -404
  69. package/test/modifiers.test.ts +0 -417
  70. package/test/parser.test.ts +0 -564
  71. package/test/performance-regression.test.ts +0 -376
  72. package/test/performance.test.ts +0 -568
  73. package/test/plugin.test.ts +0 -160
  74. package/test/scanner.test.ts +0 -94
  75. package/test/sizing.test.ts +0 -481
  76. package/test/spacing.test.ts +0 -394
  77. package/test/transformer-compile-class.test.ts +0 -287
  78. package/test/transforms.test.ts +0 -448
  79. package/test/typography.test.ts +0 -632
  80. package/test/variants-form-states.test.ts +0 -225
  81. package/test/variants-group-peer.test.ts +0 -66
  82. package/test/variants-media.test.ts +0 -213
  83. package/test/variants-positional.test.ts +0 -58
  84. package/test/variants-pseudo-elements.test.ts +0 -47
  85. package/test/variants-state.test.ts +0 -62
  86. package/tsconfig.json +0 -18
package/src/generator.ts DELETED
@@ -1,2173 +0,0 @@
1
- import type { CSSRule, CrosswindConfig, ParsedClass } from './types'
2
- import type { UtilityRule } from './rules'
3
- import { parseClass } from './parser'
4
- import { builtInRules } from './rules'
5
-
6
- /**
7
- * Deep merge objects
8
- */
9
- function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
10
- const result = { ...target }
11
- for (const key in source) {
12
- const sourceValue = source[key]
13
- const targetValue = result[key]
14
- if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue) && targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
15
- result[key] = deepMerge(targetValue, sourceValue)
16
- }
17
- else if (sourceValue !== undefined) {
18
- result[key] = sourceValue as any
19
- }
20
- }
21
- return result
22
- }
23
-
24
- // =============================================================================
25
- // STATIC UTILITY MAPS - Pre-computed at module load for O(1) lookup
26
- // These never change and are shared across all CSSGenerator instances
27
- // =============================================================================
28
-
29
- // Display utilities - direct raw class to CSS value
30
- const DISPLAY_MAP: Record<string, Record<string, string>> = {
31
- 'block': { display: 'block' },
32
- 'inline-block': { display: 'inline-block' },
33
- 'inline': { display: 'inline' },
34
- 'flex': { display: 'flex' },
35
- 'inline-flex': { display: 'inline-flex' },
36
- 'grid': { display: 'grid' },
37
- 'inline-grid': { display: 'inline-grid' },
38
- 'hidden': { display: 'none' },
39
- 'none': { display: 'none' },
40
- }
41
-
42
- // Flex direction - direct raw class to CSS
43
- const FLEX_DIRECTION_MAP: Record<string, Record<string, string>> = {
44
- 'flex-row': { 'flex-direction': 'row' },
45
- 'flex-row-reverse': { 'flex-direction': 'row-reverse' },
46
- 'flex-col': { 'flex-direction': 'column' },
47
- 'flex-col-reverse': { 'flex-direction': 'column-reverse' },
48
- }
49
-
50
- // Flex wrap - direct raw class to CSS
51
- const FLEX_WRAP_MAP: Record<string, Record<string, string>> = {
52
- 'flex-wrap': { 'flex-wrap': 'wrap' },
53
- 'flex-wrap-reverse': { 'flex-wrap': 'wrap-reverse' },
54
- 'flex-nowrap': { 'flex-wrap': 'nowrap' },
55
- }
56
-
57
- // Flex values - direct raw class to CSS
58
- const FLEX_VALUES_MAP: Record<string, Record<string, string>> = {
59
- 'flex-1': { flex: '1 1 0%' },
60
- 'flex-auto': { flex: '1 1 auto' },
61
- 'flex-initial': { flex: '0 1 auto' },
62
- 'flex-none': { flex: 'none' },
63
- }
64
-
65
- // Grow/Shrink - direct raw class to CSS
66
- const GROW_SHRINK_MAP: Record<string, Record<string, string>> = {
67
- 'grow': { 'flex-grow': '1' },
68
- 'grow-0': { 'flex-grow': '0' },
69
- 'shrink': { 'flex-shrink': '1' },
70
- 'shrink-0': { 'flex-shrink': '0' },
71
- }
72
-
73
- // Justify content - utility="justify", value lookup
74
- const JUSTIFY_CONTENT_VALUES: Record<string, string> = {
75
- start: 'flex-start',
76
- end: 'flex-end',
77
- center: 'center',
78
- between: 'space-between',
79
- around: 'space-around',
80
- evenly: 'space-evenly',
81
- }
82
-
83
- // Align items - utility="items", value lookup
84
- const ALIGN_ITEMS_VALUES: Record<string, string> = {
85
- start: 'flex-start',
86
- end: 'flex-end',
87
- center: 'center',
88
- baseline: 'baseline',
89
- stretch: 'stretch',
90
- }
91
-
92
- // Align content - utility="content", value lookup
93
- const ALIGN_CONTENT_VALUES: Record<string, string> = {
94
- normal: 'normal',
95
- center: 'center',
96
- start: 'flex-start',
97
- end: 'flex-end',
98
- between: 'space-between',
99
- around: 'space-around',
100
- evenly: 'space-evenly',
101
- baseline: 'baseline',
102
- stretch: 'stretch',
103
- }
104
-
105
- // Align self - utility="self", value lookup
106
- const ALIGN_SELF_VALUES: Record<string, string> = {
107
- auto: 'auto',
108
- start: 'flex-start',
109
- end: 'flex-end',
110
- center: 'center',
111
- stretch: 'stretch',
112
- baseline: 'baseline',
113
- }
114
-
115
- // Border style - direct raw class to CSS
116
- const BORDER_STYLE_MAP: Record<string, Record<string, string>> = {
117
- 'border-solid': { 'border-style': 'solid' },
118
- 'border-dashed': { 'border-style': 'dashed' },
119
- 'border-dotted': { 'border-style': 'dotted' },
120
- 'border-double': { 'border-style': 'double' },
121
- 'border-none': { 'border-style': 'none' },
122
- }
123
-
124
- // Transform utilities - direct raw class to CSS
125
- const TRANSFORM_MAP: Record<string, Record<string, string>> = {
126
- 'transform': { transform: 'translate(var(--hw-translate-x), var(--hw-translate-y)) rotate(var(--hw-rotate)) skewX(var(--hw-skew-x)) skewY(var(--hw-skew-y)) scaleX(var(--hw-scale-x)) scaleY(var(--hw-scale-y))' },
127
- 'transform-cpu': { transform: 'translate(var(--hw-translate-x), var(--hw-translate-y)) rotate(var(--hw-rotate)) skewX(var(--hw-skew-x)) skewY(var(--hw-skew-y)) scaleX(var(--hw-scale-x)) scaleY(var(--hw-scale-y))' },
128
- 'transform-gpu': { transform: 'translate3d(var(--hw-translate-x), var(--hw-translate-y), 0) rotate(var(--hw-rotate)) skewX(var(--hw-skew-x)) skewY(var(--hw-skew-y)) scaleX(var(--hw-scale-x)) scaleY(var(--hw-scale-y))' },
129
- 'transform-none': { transform: 'none' },
130
- }
131
-
132
- // Transition utilities - direct raw class to CSS
133
- const TRANSITION_MAP: Record<string, Record<string, string>> = {
134
- 'transition-none': { 'transition-property': 'none' },
135
- 'transition-all': { 'transition-property': 'all' },
136
- 'transition': { 'transition-property': 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter' },
137
- 'transition-colors': { 'transition-property': 'color, background-color, border-color, text-decoration-color, fill, stroke' },
138
- 'transition-opacity': { 'transition-property': 'opacity' },
139
- 'transition-shadow': { 'transition-property': 'box-shadow' },
140
- 'transition-transform': { 'transition-property': 'transform' },
141
- }
142
-
143
- // Timing functions - direct raw class to CSS
144
- const TIMING_MAP: Record<string, Record<string, string>> = {
145
- 'ease-linear': { 'transition-timing-function': 'linear' },
146
- 'ease-in': { 'transition-timing-function': 'cubic-bezier(0.4, 0, 1, 1)' },
147
- 'ease-out': { 'transition-timing-function': 'cubic-bezier(0, 0, 0.2, 1)' },
148
- 'ease-in-out': { 'transition-timing-function': 'cubic-bezier(0.4, 0, 0.2, 1)' },
149
- }
150
-
151
- // Animation presets
152
- const ANIMATION_MAP: Record<string, Record<string, string>> = {
153
- 'animate-none': { animation: 'none' },
154
- 'animate-spin': { animation: 'spin 1s linear infinite' },
155
- 'animate-ping': { animation: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite' },
156
- 'animate-pulse': { animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite' },
157
- 'animate-bounce': { animation: 'bounce 1s infinite' },
158
- }
159
-
160
- // Transform origin - utility="origin", value lookup
161
- const TRANSFORM_ORIGIN_VALUES: Record<string, string> = {
162
- 'center': 'center',
163
- 'top': 'top',
164
- 'top-right': 'top right',
165
- 'right': 'right',
166
- 'bottom-right': 'bottom right',
167
- 'bottom': 'bottom',
168
- 'bottom-left': 'bottom left',
169
- 'left': 'left',
170
- 'top-left': 'top left',
171
- }
172
-
173
- // Grid template columns - utility="grid-cols", value lookup
174
- const GRID_COLS_VALUES: Record<string, string> = {
175
- '1': 'repeat(1, minmax(0, 1fr))',
176
- '2': 'repeat(2, minmax(0, 1fr))',
177
- '3': 'repeat(3, minmax(0, 1fr))',
178
- '4': 'repeat(4, minmax(0, 1fr))',
179
- '5': 'repeat(5, minmax(0, 1fr))',
180
- '6': 'repeat(6, minmax(0, 1fr))',
181
- '7': 'repeat(7, minmax(0, 1fr))',
182
- '8': 'repeat(8, minmax(0, 1fr))',
183
- '9': 'repeat(9, minmax(0, 1fr))',
184
- '10': 'repeat(10, minmax(0, 1fr))',
185
- '11': 'repeat(11, minmax(0, 1fr))',
186
- '12': 'repeat(12, minmax(0, 1fr))',
187
- 'none': 'none',
188
- 'subgrid': 'subgrid',
189
- }
190
-
191
- // Grid template rows - utility="grid-rows", value lookup
192
- const GRID_ROWS_VALUES: Record<string, string> = {
193
- '1': 'repeat(1, minmax(0, 1fr))',
194
- '2': 'repeat(2, minmax(0, 1fr))',
195
- '3': 'repeat(3, minmax(0, 1fr))',
196
- '4': 'repeat(4, minmax(0, 1fr))',
197
- '5': 'repeat(5, minmax(0, 1fr))',
198
- '6': 'repeat(6, minmax(0, 1fr))',
199
- 'none': 'none',
200
- 'subgrid': 'subgrid',
201
- }
202
-
203
- // Grid column span - utility="col", value lookup
204
- const GRID_COL_VALUES: Record<string, string> = {
205
- 'auto': 'auto',
206
- 'span-1': 'span 1 / span 1',
207
- 'span-2': 'span 2 / span 2',
208
- 'span-3': 'span 3 / span 3',
209
- 'span-4': 'span 4 / span 4',
210
- 'span-5': 'span 5 / span 5',
211
- 'span-6': 'span 6 / span 6',
212
- 'span-7': 'span 7 / span 7',
213
- 'span-8': 'span 8 / span 8',
214
- 'span-9': 'span 9 / span 9',
215
- 'span-10': 'span 10 / span 10',
216
- 'span-11': 'span 11 / span 11',
217
- 'span-12': 'span 12 / span 12',
218
- 'span-full': '1 / -1',
219
- }
220
-
221
- // Grid row span - utility="row", value lookup
222
- const GRID_ROW_VALUES: Record<string, string> = {
223
- 'auto': 'auto',
224
- 'span-1': 'span 1 / span 1',
225
- 'span-2': 'span 2 / span 2',
226
- 'span-3': 'span 3 / span 3',
227
- 'span-4': 'span 4 / span 4',
228
- 'span-5': 'span 5 / span 5',
229
- 'span-6': 'span 6 / span 6',
230
- 'span-full': '1 / -1',
231
- }
232
-
233
- // Grid auto flow - utility="grid-flow", value lookup
234
- const GRID_FLOW_VALUES: Record<string, string> = {
235
- 'row': 'row',
236
- 'col': 'column',
237
- 'dense': 'dense',
238
- 'row-dense': 'row dense',
239
- 'col-dense': 'column dense',
240
- }
241
-
242
- // Auto cols/rows - utility="auto-cols"/"auto-rows", value lookup
243
- const AUTO_COLS_ROWS_VALUES: Record<string, string> = {
244
- 'auto': 'auto',
245
- 'min': 'min-content',
246
- 'max': 'max-content',
247
- 'fr': 'minmax(0, 1fr)',
248
- }
249
-
250
- // Place content/items/self - utility="place", value lookup
251
- const PLACE_CONTENT_VALUES: Record<string, string> = {
252
- 'center': 'center',
253
- 'start': 'start',
254
- 'end': 'end',
255
- 'between': 'space-between',
256
- 'around': 'space-around',
257
- 'evenly': 'space-evenly',
258
- 'stretch': 'stretch',
259
- }
260
-
261
- // Ring utilities - direct raw class to CSS
262
- const RING_MAP: Record<string, Record<string, string>> = {
263
- 'ring': { 'box-shadow': '0 0 0 3px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
264
- 'ring-0': { 'box-shadow': '0 0 0 0px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
265
- 'ring-1': { 'box-shadow': '0 0 0 1px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
266
- 'ring-2': { 'box-shadow': '0 0 0 2px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
267
- 'ring-4': { 'box-shadow': '0 0 0 4px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
268
- 'ring-8': { 'box-shadow': '0 0 0 8px var(--hw-ring-color, rgba(59, 130, 246, 0.5))' },
269
- 'ring-inset': { '--hw-ring-inset': 'inset' },
270
- }
271
-
272
- // Shadow utilities - use CSS variable system for shadow color support
273
- // --hw-shadow holds the default shadow, --hw-shadow-colored replaces colors with var(--hw-shadow-color)
274
- // box-shadow references --hw-shadow which can be swapped to --hw-shadow-colored by a shadow-{color} utility
275
- const SHADOW_MAP: Record<string, Record<string, string>> = {
276
- 'shadow-sm': {
277
- '--hw-shadow': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
278
- '--hw-shadow-colored': '0 1px 2px 0 var(--hw-shadow-color)',
279
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
280
- },
281
- 'shadow': {
282
- '--hw-shadow': '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
283
- '--hw-shadow-colored': '0 1px 3px 0 var(--hw-shadow-color), 0 1px 2px -1px var(--hw-shadow-color)',
284
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
285
- },
286
- 'shadow-md': {
287
- '--hw-shadow': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
288
- '--hw-shadow-colored': '0 4px 6px -1px var(--hw-shadow-color), 0 2px 4px -2px var(--hw-shadow-color)',
289
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
290
- },
291
- 'shadow-lg': {
292
- '--hw-shadow': '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
293
- '--hw-shadow-colored': '0 10px 15px -3px var(--hw-shadow-color), 0 4px 6px -4px var(--hw-shadow-color)',
294
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
295
- },
296
- 'shadow-xl': {
297
- '--hw-shadow': '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
298
- '--hw-shadow-colored': '0 20px 25px -5px var(--hw-shadow-color), 0 8px 10px -6px var(--hw-shadow-color)',
299
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
300
- },
301
- 'shadow-2xl': {
302
- '--hw-shadow': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
303
- '--hw-shadow-colored': '0 25px 50px -12px var(--hw-shadow-color)',
304
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
305
- },
306
- 'shadow-inner': {
307
- '--hw-shadow': 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
308
- '--hw-shadow-colored': 'inset 0 2px 4px 0 var(--hw-shadow-color)',
309
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
310
- },
311
- 'shadow-none': {
312
- '--hw-shadow': '0 0 #0000',
313
- 'box-shadow': 'var(--hw-ring-offset-shadow, 0 0 #0000), var(--hw-ring-shadow, 0 0 #0000), var(--hw-shadow)',
314
- },
315
- }
316
-
317
- // Border radius - direct raw class to CSS
318
- const BORDER_RADIUS_MAP: Record<string, Record<string, string>> = {
319
- 'rounded-none': { 'border-radius': '0px' },
320
- 'rounded-sm': { 'border-radius': '0.125rem' },
321
- 'rounded': { 'border-radius': '0.25rem' },
322
- 'rounded-md': { 'border-radius': '0.375rem' },
323
- 'rounded-lg': { 'border-radius': '0.5rem' },
324
- 'rounded-xl': { 'border-radius': '0.75rem' },
325
- 'rounded-2xl': { 'border-radius': '1rem' },
326
- 'rounded-3xl': { 'border-radius': '1.5rem' },
327
- 'rounded-full': { 'border-radius': '9999px' },
328
- // Corner-specific rounded utilities
329
- 'rounded-t-none': { 'border-top-left-radius': '0px', 'border-top-right-radius': '0px' },
330
- 'rounded-t-sm': { 'border-top-left-radius': '0.125rem', 'border-top-right-radius': '0.125rem' },
331
- 'rounded-t-lg': { 'border-top-left-radius': '0.5rem', 'border-top-right-radius': '0.5rem' },
332
- 'rounded-r-lg': { 'border-top-right-radius': '0.5rem', 'border-bottom-right-radius': '0.5rem' },
333
- 'rounded-b-lg': { 'border-bottom-left-radius': '0.5rem', 'border-bottom-right-radius': '0.5rem' },
334
- 'rounded-l-lg': { 'border-top-left-radius': '0.5rem', 'border-bottom-left-radius': '0.5rem' },
335
- 'rounded-tl-lg': { 'border-top-left-radius': '0.5rem' },
336
- 'rounded-tr-lg': { 'border-top-right-radius': '0.5rem' },
337
- 'rounded-br-lg': { 'border-bottom-right-radius': '0.5rem' },
338
- 'rounded-bl-lg': { 'border-bottom-left-radius': '0.5rem' },
339
- }
340
-
341
- // Border width - direct raw class to CSS
342
- const BORDER_WIDTH_MAP: Record<string, Record<string, string>> = {
343
- 'border': { 'border-width': '1px' },
344
- 'border-0': { 'border-width': '0px' },
345
- 'border-2': { 'border-width': '2px' },
346
- 'border-4': { 'border-width': '4px' },
347
- 'border-8': { 'border-width': '8px' },
348
- // Side-specific border width
349
- 'border-t': { 'border-top-width': '1px' },
350
- 'border-r': { 'border-right-width': '1px' },
351
- 'border-b': { 'border-bottom-width': '1px' },
352
- 'border-l': { 'border-left-width': '1px' },
353
- 'border-t-0': { 'border-top-width': '0px' },
354
- 'border-r-0': { 'border-right-width': '0px' },
355
- 'border-b-0': { 'border-bottom-width': '0px' },
356
- 'border-l-0': { 'border-left-width': '0px' },
357
- 'border-t-2': { 'border-top-width': '2px' },
358
- 'border-r-2': { 'border-right-width': '2px' },
359
- 'border-b-2': { 'border-bottom-width': '2px' },
360
- 'border-l-2': { 'border-left-width': '2px' },
361
- 'border-t-4': { 'border-top-width': '4px' },
362
- 'border-r-4': { 'border-right-width': '4px' },
363
- 'border-b-4': { 'border-bottom-width': '4px' },
364
- 'border-l-4': { 'border-left-width': '4px' },
365
- 'border-x': { 'border-left-width': '1px', 'border-right-width': '1px' },
366
- 'border-y': { 'border-top-width': '1px', 'border-bottom-width': '1px' },
367
- 'border-x-0': { 'border-left-width': '0px', 'border-right-width': '0px' },
368
- 'border-y-0': { 'border-top-width': '0px', 'border-bottom-width': '0px' },
369
- 'border-x-2': { 'border-left-width': '2px', 'border-right-width': '2px' },
370
- 'border-y-2': { 'border-top-width': '2px', 'border-bottom-width': '2px' },
371
- }
372
-
373
- // Ring offset - direct raw class to CSS
374
- const RING_OFFSET_MAP: Record<string, Record<string, string>> = {
375
- 'ring-offset-0': { '--hw-ring-offset-width': '0px' },
376
- 'ring-offset-1': { '--hw-ring-offset-width': '1px' },
377
- 'ring-offset-2': { '--hw-ring-offset-width': '2px' },
378
- 'ring-offset-4': { '--hw-ring-offset-width': '4px' },
379
- 'ring-offset-8': { '--hw-ring-offset-width': '8px' },
380
- }
381
-
382
- // Opacity utilities - direct raw class to CSS
383
- const OPACITY_MAP: Record<string, Record<string, string>> = {
384
- 'opacity-0': { opacity: '0' },
385
- 'opacity-5': { opacity: '0.05' },
386
- 'opacity-10': { opacity: '0.1' },
387
- 'opacity-20': { opacity: '0.2' },
388
- 'opacity-25': { opacity: '0.25' },
389
- 'opacity-30': { opacity: '0.3' },
390
- 'opacity-40': { opacity: '0.4' },
391
- 'opacity-50': { opacity: '0.5' },
392
- 'opacity-60': { opacity: '0.6' },
393
- 'opacity-70': { opacity: '0.7' },
394
- 'opacity-75': { opacity: '0.75' },
395
- 'opacity-80': { opacity: '0.8' },
396
- 'opacity-90': { opacity: '0.9' },
397
- 'opacity-95': { opacity: '0.95' },
398
- 'opacity-100': { opacity: '1' },
399
- }
400
-
401
- // Pre-computed spacing values for O(1) lookup
402
- const SPACING_VALUES: Record<string, string> = {
403
- '0': '0',
404
- 'px': '1px',
405
- '0.5': '0.125rem',
406
- '1': '0.25rem',
407
- '1.5': '0.375rem',
408
- '2': '0.5rem',
409
- '2.5': '0.625rem',
410
- '3': '0.75rem',
411
- '3.5': '0.875rem',
412
- '4': '1rem',
413
- '5': '1.25rem',
414
- '6': '1.5rem',
415
- '7': '1.75rem',
416
- '8': '2rem',
417
- '9': '2.25rem',
418
- '10': '2.5rem',
419
- '11': '2.75rem',
420
- '12': '3rem',
421
- '14': '3.5rem',
422
- '16': '4rem',
423
- '20': '5rem',
424
- '24': '6rem',
425
- '28': '7rem',
426
- '32': '8rem',
427
- '36': '9rem',
428
- '40': '10rem',
429
- '44': '11rem',
430
- '48': '12rem',
431
- '52': '13rem',
432
- '56': '14rem',
433
- '60': '15rem',
434
- '64': '16rem',
435
- '72': '18rem',
436
- '80': '20rem',
437
- '96': '24rem',
438
- }
439
-
440
- // Size values (spacing + special values)
441
- const SIZE_VALUES: Record<string, string> = {
442
- ...SPACING_VALUES,
443
- 'auto': 'auto',
444
- 'full': '100%',
445
- 'screen': '100vw',
446
- 'min': 'min-content',
447
- 'max': 'max-content',
448
- 'fit': 'fit-content',
449
- // Common fractions (using same precision as original implementation)
450
- '1/2': '50%',
451
- '1/3': '33.33333333333333%',
452
- '2/3': '66.66666666666666%',
453
- '1/4': '25%',
454
- '2/4': '50%',
455
- '3/4': '75%',
456
- '1/5': '20%',
457
- '2/5': '40%',
458
- '3/5': '60%',
459
- '4/5': '80%',
460
- '1/6': '16.666666666666664%',
461
- '2/6': '33.33333333333333%',
462
- '3/6': '50%',
463
- '4/6': '66.66666666666666%',
464
- '5/6': '83.33333333333334%',
465
- }
466
-
467
- // Pre-computed color values for common Tailwind colors (O(1) lookup)
468
- // These are the most commonly used colors in the benchmark
469
- const COMMON_COLORS: Record<string, string> = {
470
- // Gray
471
- 'gray-50': 'oklch(98.5% 0.002 247.839)', 'gray-100': 'oklch(96.7% 0.003 264.542)',
472
- 'gray-200': 'oklch(92.8% 0.006 264.531)', 'gray-300': 'oklch(87.2% 0.01 258.338)',
473
- 'gray-400': 'oklch(70.7% 0.022 261.325)', 'gray-500': 'oklch(55.1% 0.027 264.364)',
474
- 'gray-600': 'oklch(44.6% 0.03 256.802)', 'gray-700': 'oklch(37.3% 0.034 259.733)',
475
- 'gray-800': 'oklch(27.8% 0.033 256.848)', 'gray-900': 'oklch(21% 0.034 264.665)',
476
- 'gray-950': 'oklch(13% 0.028 261.692)',
477
- // Red
478
- 'red-50': 'oklch(97.1% 0.013 17.38)', 'red-100': 'oklch(93.6% 0.032 17.717)',
479
- 'red-200': 'oklch(88.5% 0.062 18.334)', 'red-300': 'oklch(80.8% 0.114 19.571)',
480
- 'red-400': 'oklch(70.4% 0.191 22.216)', 'red-500': 'oklch(63.7% 0.237 25.331)',
481
- 'red-600': 'oklch(57.7% 0.245 27.325)', 'red-700': 'oklch(50.5% 0.213 27.518)',
482
- 'red-800': 'oklch(44.4% 0.177 26.899)', 'red-900': 'oklch(39.6% 0.141 25.723)',
483
- 'red-950': 'oklch(25.8% 0.092 26.042)',
484
- // Blue
485
- 'blue-50': 'oklch(97% 0.014 254.604)', 'blue-100': 'oklch(93.2% 0.032 255.585)',
486
- 'blue-200': 'oklch(88.2% 0.059 254.128)', 'blue-300': 'oklch(80.9% 0.105 251.813)',
487
- 'blue-400': 'oklch(70.7% 0.165 254.624)', 'blue-500': 'oklch(62.3% 0.214 259.815)',
488
- 'blue-600': 'oklch(54.6% 0.245 262.881)', 'blue-700': 'oklch(48.8% 0.243 264.376)',
489
- 'blue-800': 'oklch(42.4% 0.199 265.638)', 'blue-900': 'oklch(37.9% 0.146 265.522)',
490
- 'blue-950': 'oklch(28.2% 0.091 267.935)',
491
- // Green
492
- 'green-50': 'oklch(98.2% 0.018 155.826)', 'green-100': 'oklch(96.2% 0.044 156.743)',
493
- 'green-200': 'oklch(92.5% 0.084 155.995)', 'green-300': 'oklch(87.1% 0.15 154.449)',
494
- 'green-400': 'oklch(79.2% 0.209 151.711)', 'green-500': 'oklch(72.3% 0.219 149.579)',
495
- 'green-600': 'oklch(62.7% 0.194 149.214)', 'green-700': 'oklch(52.7% 0.154 150.069)',
496
- 'green-800': 'oklch(44.8% 0.119 151.328)', 'green-900': 'oklch(39.3% 0.095 152.535)',
497
- 'green-950': 'oklch(26.6% 0.065 152.934)',
498
- // Yellow
499
- 'yellow-50': 'oklch(98.7% 0.026 102.212)', 'yellow-100': 'oklch(97.3% 0.071 103.193)',
500
- 'yellow-200': 'oklch(94.5% 0.129 101.54)', 'yellow-300': 'oklch(90.5% 0.182 98.111)',
501
- 'yellow-400': 'oklch(85.2% 0.199 91.936)', 'yellow-500': 'oklch(79.5% 0.184 86.047)',
502
- 'yellow-600': 'oklch(68.1% 0.162 75.834)', 'yellow-700': 'oklch(55.4% 0.135 66.442)',
503
- 'yellow-800': 'oklch(47.6% 0.114 61.907)', 'yellow-900': 'oklch(42.1% 0.095 57.708)',
504
- 'yellow-950': 'oklch(28.6% 0.066 53.813)',
505
- // Purple
506
- 'purple-50': 'oklch(97.7% 0.014 308.299)', 'purple-100': 'oklch(94.6% 0.033 307.174)',
507
- 'purple-200': 'oklch(90.2% 0.063 306.703)', 'purple-300': 'oklch(82.7% 0.119 306.383)',
508
- 'purple-400': 'oklch(71.4% 0.203 305.504)', 'purple-500': 'oklch(62.7% 0.265 303.9)',
509
- 'purple-600': 'oklch(55.8% 0.288 302.321)', 'purple-700': 'oklch(49.6% 0.265 301.924)',
510
- 'purple-800': 'oklch(43.8% 0.218 303.724)', 'purple-900': 'oklch(38.1% 0.176 304.987)',
511
- 'purple-950': 'oklch(29.1% 0.149 302.717)',
512
- // Pink
513
- 'pink-50': 'oklch(97.1% 0.014 343.198)', 'pink-100': 'oklch(94.8% 0.028 342.258)',
514
- 'pink-200': 'oklch(89.9% 0.061 343.231)', 'pink-300': 'oklch(82.3% 0.116 346.018)',
515
- 'pink-400': 'oklch(71.8% 0.202 349.761)', 'pink-500': 'oklch(65.6% 0.241 354.308)',
516
- 'pink-600': 'oklch(59.2% 0.249 0.584)', 'pink-700': 'oklch(52.5% 0.223 3.958)',
517
- 'pink-800': 'oklch(45.9% 0.187 3.815)', 'pink-900': 'oklch(40.8% 0.153 2.432)',
518
- 'pink-950': 'oklch(28.4% 0.109 3.907)',
519
- // Indigo
520
- 'indigo-50': 'oklch(96.2% 0.018 272.314)', 'indigo-100': 'oklch(93% 0.034 272.788)',
521
- 'indigo-200': 'oklch(87% 0.065 274.039)', 'indigo-300': 'oklch(78.5% 0.115 274.713)',
522
- 'indigo-400': 'oklch(67.3% 0.182 276.935)', 'indigo-500': 'oklch(58.5% 0.233 277.117)',
523
- 'indigo-600': 'oklch(51.1% 0.262 276.966)', 'indigo-700': 'oklch(45.7% 0.24 277.023)',
524
- 'indigo-800': 'oklch(39.8% 0.195 277.366)', 'indigo-900': 'oklch(35.9% 0.144 278.697)',
525
- 'indigo-950': 'oklch(26.9% 0.096 280.79)',
526
- // Cyan
527
- 'cyan-50': 'oklch(98.4% 0.019 200.873)', 'cyan-100': 'oklch(95.6% 0.045 203.388)',
528
- 'cyan-200': 'oklch(91.7% 0.08 205.041)', 'cyan-300': 'oklch(86.5% 0.127 207.078)',
529
- 'cyan-400': 'oklch(78.9% 0.154 211.53)', 'cyan-500': 'oklch(71.5% 0.143 215.221)',
530
- 'cyan-600': 'oklch(60.9% 0.126 221.723)', 'cyan-700': 'oklch(52% 0.105 223.128)',
531
- 'cyan-800': 'oklch(45% 0.085 224.283)', 'cyan-900': 'oklch(39.8% 0.07 227.392)',
532
- 'cyan-950': 'oklch(30.2% 0.056 229.695)',
533
- // Emerald
534
- 'emerald-50': 'oklch(97.9% 0.021 166.113)', 'emerald-100': 'oklch(95% 0.052 163.051)',
535
- 'emerald-200': 'oklch(90.5% 0.093 164.15)', 'emerald-300': 'oklch(84.5% 0.143 164.978)',
536
- 'emerald-400': 'oklch(76.5% 0.177 163.223)', 'emerald-500': 'oklch(69.6% 0.17 162.48)',
537
- 'emerald-600': 'oklch(59.6% 0.145 163.225)', 'emerald-700': 'oklch(50.8% 0.118 165.612)',
538
- 'emerald-800': 'oklch(43.2% 0.095 166.913)', 'emerald-900': 'oklch(37.8% 0.077 168.94)',
539
- 'emerald-950': 'oklch(26.2% 0.051 172.552)',
540
- // Special colors
541
- 'white': '#fff', 'black': '#000', 'transparent': 'transparent', 'current': 'currentColor',
542
- }
543
-
544
- // =============================================================================
545
- // TYPOGRAPHY UTILITIES - Fast path for font/text utilities
546
- // =============================================================================
547
-
548
- // Font weight - direct raw class to CSS
549
- const FONT_WEIGHT_MAP: Record<string, Record<string, string>> = {
550
- 'font-thin': { 'font-weight': '100' },
551
- 'font-extralight': { 'font-weight': '200' },
552
- 'font-light': { 'font-weight': '300' },
553
- 'font-normal': { 'font-weight': '400' },
554
- 'font-medium': { 'font-weight': '500' },
555
- 'font-semibold': { 'font-weight': '600' },
556
- 'font-bold': { 'font-weight': '700' },
557
- 'font-extrabold': { 'font-weight': '800' },
558
- 'font-black': { 'font-weight': '900' },
559
- }
560
-
561
- // Font style - direct raw class to CSS
562
- const FONT_STYLE_MAP: Record<string, Record<string, string>> = {
563
- 'italic': { 'font-style': 'italic' },
564
- 'not-italic': { 'font-style': 'normal' },
565
- }
566
-
567
- // Text transform - direct raw class to CSS
568
- const TEXT_TRANSFORM_MAP: Record<string, Record<string, string>> = {
569
- 'uppercase': { 'text-transform': 'uppercase' },
570
- 'lowercase': { 'text-transform': 'lowercase' },
571
- 'capitalize': { 'text-transform': 'capitalize' },
572
- 'normal-case': { 'text-transform': 'none' },
573
- }
574
-
575
- // Text decoration line - direct raw class to CSS
576
- const TEXT_DECORATION_MAP: Record<string, Record<string, string>> = {
577
- 'underline': { 'text-decoration-line': 'underline' },
578
- 'overline': { 'text-decoration-line': 'overline' },
579
- 'line-through': { 'text-decoration-line': 'line-through' },
580
- 'no-underline': { 'text-decoration-line': 'none' },
581
- }
582
-
583
- // Text decoration style - direct raw class to CSS
584
- const TEXT_DECORATION_STYLE_MAP: Record<string, Record<string, string>> = {
585
- 'decoration-solid': { 'text-decoration-style': 'solid' },
586
- 'decoration-double': { 'text-decoration-style': 'double' },
587
- 'decoration-dotted': { 'text-decoration-style': 'dotted' },
588
- 'decoration-dashed': { 'text-decoration-style': 'dashed' },
589
- 'decoration-wavy': { 'text-decoration-style': 'wavy' },
590
- }
591
-
592
- // Text align - direct raw class to CSS
593
- const TEXT_ALIGN_MAP: Record<string, Record<string, string>> = {
594
- 'text-left': { 'text-align': 'left' },
595
- 'text-center': { 'text-align': 'center' },
596
- 'text-right': { 'text-align': 'right' },
597
- 'text-justify': { 'text-align': 'justify' },
598
- 'text-start': { 'text-align': 'start' },
599
- 'text-end': { 'text-align': 'end' },
600
- }
601
-
602
- // Text overflow - direct raw class to CSS
603
- const TEXT_OVERFLOW_MAP: Record<string, Record<string, string>> = {
604
- 'truncate': { 'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap' },
605
- 'text-ellipsis': { 'text-overflow': 'ellipsis' },
606
- 'text-clip': { 'text-overflow': 'clip' },
607
- }
608
-
609
- // Text wrap - direct raw class to CSS
610
- const TEXT_WRAP_MAP: Record<string, Record<string, string>> = {
611
- 'text-wrap': { 'text-wrap': 'wrap' },
612
- 'text-nowrap': { 'text-wrap': 'nowrap' },
613
- 'text-balance': { 'text-wrap': 'balance' },
614
- 'text-pretty': { 'text-wrap': 'pretty' },
615
- }
616
-
617
- // Whitespace - direct raw class to CSS
618
- const WHITESPACE_MAP: Record<string, Record<string, string>> = {
619
- 'whitespace-normal': { 'white-space': 'normal' },
620
- 'whitespace-nowrap': { 'white-space': 'nowrap' },
621
- 'whitespace-pre': { 'white-space': 'pre' },
622
- 'whitespace-pre-line': { 'white-space': 'pre-line' },
623
- 'whitespace-pre-wrap': { 'white-space': 'pre-wrap' },
624
- 'whitespace-break-spaces': { 'white-space': 'break-spaces' },
625
- }
626
-
627
- // Word break - direct raw class to CSS
628
- const WORD_BREAK_MAP: Record<string, Record<string, string>> = {
629
- 'break-normal': { 'overflow-wrap': 'normal', 'word-break': 'normal' },
630
- 'break-words': { 'overflow-wrap': 'break-word' },
631
- 'break-all': { 'word-break': 'break-all' },
632
- 'break-keep': { 'word-break': 'keep-all' },
633
- }
634
-
635
- // Vertical align - direct raw class to CSS
636
- const VERTICAL_ALIGN_MAP: Record<string, Record<string, string>> = {
637
- 'align-baseline': { 'vertical-align': 'baseline' },
638
- 'align-top': { 'vertical-align': 'top' },
639
- 'align-middle': { 'vertical-align': 'middle' },
640
- 'align-bottom': { 'vertical-align': 'bottom' },
641
- 'align-text-top': { 'vertical-align': 'text-top' },
642
- 'align-text-bottom': { 'vertical-align': 'text-bottom' },
643
- 'align-sub': { 'vertical-align': 'sub' },
644
- 'align-super': { 'vertical-align': 'super' },
645
- }
646
-
647
- // List style type - direct raw class to CSS
648
- const LIST_STYLE_MAP: Record<string, Record<string, string>> = {
649
- 'list-none': { 'list-style-type': 'none' },
650
- 'list-disc': { 'list-style-type': 'disc' },
651
- 'list-decimal': { 'list-style-type': 'decimal' },
652
- 'list-inside': { 'list-style-position': 'inside' },
653
- 'list-outside': { 'list-style-position': 'outside' },
654
- }
655
-
656
- // Hyphens - direct raw class to CSS
657
- const HYPHENS_MAP: Record<string, Record<string, string>> = {
658
- 'hyphens-none': { hyphens: 'none' },
659
- 'hyphens-manual': { hyphens: 'manual' },
660
- 'hyphens-auto': { hyphens: 'auto' },
661
- }
662
-
663
- // =============================================================================
664
- // LAYOUT UTILITIES - Fast path for layout/positioning
665
- // =============================================================================
666
-
667
- // Position - direct raw class to CSS
668
- const POSITION_MAP: Record<string, Record<string, string>> = {
669
- 'static': { position: 'static' },
670
- 'fixed': { position: 'fixed' },
671
- 'absolute': { position: 'absolute' },
672
- 'relative': { position: 'relative' },
673
- 'sticky': { position: 'sticky' },
674
- }
675
-
676
- // Visibility - direct raw class to CSS
677
- const VISIBILITY_MAP: Record<string, Record<string, string>> = {
678
- 'visible': { visibility: 'visible' },
679
- 'invisible': { visibility: 'hidden' },
680
- 'collapse': { visibility: 'collapse' },
681
- }
682
-
683
- // Float - direct raw class to CSS
684
- const FLOAT_MAP: Record<string, Record<string, string>> = {
685
- 'float-left': { float: 'left' },
686
- 'float-right': { float: 'right' },
687
- 'float-none': { float: 'none' },
688
- 'float-start': { float: 'inline-start' },
689
- 'float-end': { float: 'inline-end' },
690
- }
691
-
692
- // Clear - direct raw class to CSS
693
- const CLEAR_MAP: Record<string, Record<string, string>> = {
694
- 'clear-left': { clear: 'left' },
695
- 'clear-right': { clear: 'right' },
696
- 'clear-both': { clear: 'both' },
697
- 'clear-none': { clear: 'none' },
698
- 'clear-start': { clear: 'inline-start' },
699
- 'clear-end': { clear: 'inline-end' },
700
- }
701
-
702
- // Isolation - direct raw class to CSS
703
- const ISOLATION_MAP: Record<string, Record<string, string>> = {
704
- 'isolate': { isolation: 'isolate' },
705
- 'isolation-auto': { isolation: 'auto' },
706
- }
707
-
708
- // Object fit - direct raw class to CSS
709
- const OBJECT_FIT_MAP: Record<string, Record<string, string>> = {
710
- 'object-contain': { 'object-fit': 'contain' },
711
- 'object-cover': { 'object-fit': 'cover' },
712
- 'object-fill': { 'object-fit': 'fill' },
713
- 'object-none': { 'object-fit': 'none' },
714
- 'object-scale-down': { 'object-fit': 'scale-down' },
715
- }
716
-
717
- // Object position - direct raw class to CSS
718
- const OBJECT_POSITION_MAP: Record<string, Record<string, string>> = {
719
- 'object-bottom': { 'object-position': 'bottom' },
720
- 'object-center': { 'object-position': 'center' },
721
- 'object-left': { 'object-position': 'left' },
722
- 'object-left-bottom': { 'object-position': 'left bottom' },
723
- 'object-left-top': { 'object-position': 'left top' },
724
- 'object-right': { 'object-position': 'right' },
725
- 'object-right-bottom': { 'object-position': 'right bottom' },
726
- 'object-right-top': { 'object-position': 'right top' },
727
- 'object-top': { 'object-position': 'top' },
728
- }
729
-
730
- // Overflow - direct raw class to CSS
731
- const OVERFLOW_MAP: Record<string, Record<string, string>> = {
732
- 'overflow-auto': { overflow: 'auto' },
733
- 'overflow-hidden': { overflow: 'hidden' },
734
- 'overflow-clip': { overflow: 'clip' },
735
- 'overflow-visible': { overflow: 'visible' },
736
- 'overflow-scroll': { overflow: 'scroll' },
737
- 'overflow-x-auto': { 'overflow-x': 'auto' },
738
- 'overflow-x-hidden': { 'overflow-x': 'hidden' },
739
- 'overflow-x-clip': { 'overflow-x': 'clip' },
740
- 'overflow-x-visible': { 'overflow-x': 'visible' },
741
- 'overflow-x-scroll': { 'overflow-x': 'scroll' },
742
- 'overflow-y-auto': { 'overflow-y': 'auto' },
743
- 'overflow-y-hidden': { 'overflow-y': 'hidden' },
744
- 'overflow-y-clip': { 'overflow-y': 'clip' },
745
- 'overflow-y-visible': { 'overflow-y': 'visible' },
746
- 'overflow-y-scroll': { 'overflow-y': 'scroll' },
747
- }
748
-
749
- // Overscroll - direct raw class to CSS
750
- const OVERSCROLL_MAP: Record<string, Record<string, string>> = {
751
- 'overscroll-auto': { 'overscroll-behavior': 'auto' },
752
- 'overscroll-contain': { 'overscroll-behavior': 'contain' },
753
- 'overscroll-none': { 'overscroll-behavior': 'none' },
754
- 'overscroll-x-auto': { 'overscroll-behavior-x': 'auto' },
755
- 'overscroll-x-contain': { 'overscroll-behavior-x': 'contain' },
756
- 'overscroll-x-none': { 'overscroll-behavior-x': 'none' },
757
- 'overscroll-y-auto': { 'overscroll-behavior-y': 'auto' },
758
- 'overscroll-y-contain': { 'overscroll-behavior-y': 'contain' },
759
- 'overscroll-y-none': { 'overscroll-behavior-y': 'none' },
760
- }
761
-
762
- // Z-index - direct raw class to CSS
763
- const Z_INDEX_MAP: Record<string, Record<string, string>> = {
764
- 'z-0': { 'z-index': '0' },
765
- 'z-10': { 'z-index': '10' },
766
- 'z-20': { 'z-index': '20' },
767
- 'z-30': { 'z-index': '30' },
768
- 'z-40': { 'z-index': '40' },
769
- 'z-50': { 'z-index': '50' },
770
- 'z-auto': { 'z-index': 'auto' },
771
- }
772
-
773
- // Aspect ratio - direct raw class to CSS
774
- const ASPECT_RATIO_MAP: Record<string, Record<string, string>> = {
775
- 'aspect-auto': { 'aspect-ratio': 'auto' },
776
- 'aspect-square': { 'aspect-ratio': '1 / 1' },
777
- 'aspect-video': { 'aspect-ratio': '16 / 9' },
778
- }
779
-
780
- // Box sizing - direct raw class to CSS
781
- const BOX_SIZING_MAP: Record<string, Record<string, string>> = {
782
- 'box-border': { 'box-sizing': 'border-box' },
783
- 'box-content': { 'box-sizing': 'content-box' },
784
- }
785
-
786
- // Box decoration break - direct raw class to CSS
787
- const BOX_DECORATION_MAP: Record<string, Record<string, string>> = {
788
- 'box-decoration-clone': { 'box-decoration-break': 'clone' },
789
- 'box-decoration-slice': { 'box-decoration-break': 'slice' },
790
- }
791
-
792
- // =============================================================================
793
- // BACKGROUND UTILITIES - Fast path for background
794
- // =============================================================================
795
-
796
- // Background attachment - direct raw class to CSS
797
- const BG_ATTACHMENT_MAP: Record<string, Record<string, string>> = {
798
- 'bg-fixed': { 'background-attachment': 'fixed' },
799
- 'bg-local': { 'background-attachment': 'local' },
800
- 'bg-scroll': { 'background-attachment': 'scroll' },
801
- }
802
-
803
- // Background clip - direct raw class to CSS
804
- const BG_CLIP_MAP: Record<string, Record<string, string>> = {
805
- 'bg-clip-border': { 'background-clip': 'border-box' },
806
- 'bg-clip-padding': { 'background-clip': 'padding-box' },
807
- 'bg-clip-content': { 'background-clip': 'content-box' },
808
- 'bg-clip-text': { 'background-clip': 'text', '-webkit-background-clip': 'text' },
809
- }
810
-
811
- // Background origin - direct raw class to CSS
812
- const BG_ORIGIN_MAP: Record<string, Record<string, string>> = {
813
- 'bg-origin-border': { 'background-origin': 'border-box' },
814
- 'bg-origin-padding': { 'background-origin': 'padding-box' },
815
- 'bg-origin-content': { 'background-origin': 'content-box' },
816
- }
817
-
818
- // Background position - direct raw class to CSS
819
- const BG_POSITION_MAP: Record<string, Record<string, string>> = {
820
- 'bg-bottom': { 'background-position': 'bottom' },
821
- 'bg-center': { 'background-position': 'center' },
822
- 'bg-left': { 'background-position': 'left' },
823
- 'bg-left-bottom': { 'background-position': 'left bottom' },
824
- 'bg-left-top': { 'background-position': 'left top' },
825
- 'bg-right': { 'background-position': 'right' },
826
- 'bg-right-bottom': { 'background-position': 'right bottom' },
827
- 'bg-right-top': { 'background-position': 'right top' },
828
- 'bg-top': { 'background-position': 'top' },
829
- }
830
-
831
- // Background repeat - direct raw class to CSS
832
- const BG_REPEAT_MAP: Record<string, Record<string, string>> = {
833
- 'bg-repeat': { 'background-repeat': 'repeat' },
834
- 'bg-no-repeat': { 'background-repeat': 'no-repeat' },
835
- 'bg-repeat-x': { 'background-repeat': 'repeat-x' },
836
- 'bg-repeat-y': { 'background-repeat': 'repeat-y' },
837
- 'bg-repeat-round': { 'background-repeat': 'round' },
838
- 'bg-repeat-space': { 'background-repeat': 'space' },
839
- }
840
-
841
- // Background size - direct raw class to CSS
842
- const BG_SIZE_MAP: Record<string, Record<string, string>> = {
843
- 'bg-auto': { 'background-size': 'auto' },
844
- 'bg-cover': { 'background-size': 'cover' },
845
- 'bg-contain': { 'background-size': 'contain' },
846
- }
847
-
848
- // Gradient direction - direct raw class to CSS
849
- const GRADIENT_MAP: Record<string, Record<string, string>> = {
850
- 'bg-none': { 'background-image': 'none' },
851
- 'bg-gradient-to-t': { 'background-image': 'linear-gradient(to top, var(--hw-gradient-stops))' },
852
- 'bg-gradient-to-tr': { 'background-image': 'linear-gradient(to top right, var(--hw-gradient-stops))' },
853
- 'bg-gradient-to-r': { 'background-image': 'linear-gradient(to right, var(--hw-gradient-stops))' },
854
- 'bg-gradient-to-br': { 'background-image': 'linear-gradient(to bottom right, var(--hw-gradient-stops))' },
855
- 'bg-gradient-to-b': { 'background-image': 'linear-gradient(to bottom, var(--hw-gradient-stops))' },
856
- 'bg-gradient-to-bl': { 'background-image': 'linear-gradient(to bottom left, var(--hw-gradient-stops))' },
857
- 'bg-gradient-to-l': { 'background-image': 'linear-gradient(to left, var(--hw-gradient-stops))' },
858
- 'bg-gradient-to-tl': { 'background-image': 'linear-gradient(to top left, var(--hw-gradient-stops))' },
859
- }
860
-
861
- // =============================================================================
862
- // INTERACTIVITY UTILITIES - Fast path for cursor/pointer/etc
863
- // =============================================================================
864
-
865
- // Cursor - direct raw class to CSS
866
- const CURSOR_MAP: Record<string, Record<string, string>> = {
867
- 'cursor-auto': { cursor: 'auto' },
868
- 'cursor-default': { cursor: 'default' },
869
- 'cursor-pointer': { cursor: 'pointer' },
870
- 'cursor-wait': { cursor: 'wait' },
871
- 'cursor-text': { cursor: 'text' },
872
- 'cursor-move': { cursor: 'move' },
873
- 'cursor-help': { cursor: 'help' },
874
- 'cursor-not-allowed': { cursor: 'not-allowed' },
875
- 'cursor-none': { cursor: 'none' },
876
- 'cursor-context-menu': { cursor: 'context-menu' },
877
- 'cursor-progress': { cursor: 'progress' },
878
- 'cursor-cell': { cursor: 'cell' },
879
- 'cursor-crosshair': { cursor: 'crosshair' },
880
- 'cursor-vertical-text': { cursor: 'vertical-text' },
881
- 'cursor-alias': { cursor: 'alias' },
882
- 'cursor-copy': { cursor: 'copy' },
883
- 'cursor-no-drop': { cursor: 'no-drop' },
884
- 'cursor-grab': { cursor: 'grab' },
885
- 'cursor-grabbing': { cursor: 'grabbing' },
886
- 'cursor-all-scroll': { cursor: 'all-scroll' },
887
- 'cursor-col-resize': { cursor: 'col-resize' },
888
- 'cursor-row-resize': { cursor: 'row-resize' },
889
- 'cursor-n-resize': { cursor: 'n-resize' },
890
- 'cursor-e-resize': { cursor: 'e-resize' },
891
- 'cursor-s-resize': { cursor: 's-resize' },
892
- 'cursor-w-resize': { cursor: 'w-resize' },
893
- 'cursor-ne-resize': { cursor: 'ne-resize' },
894
- 'cursor-nw-resize': { cursor: 'nw-resize' },
895
- 'cursor-se-resize': { cursor: 'se-resize' },
896
- 'cursor-sw-resize': { cursor: 'sw-resize' },
897
- 'cursor-ew-resize': { cursor: 'ew-resize' },
898
- 'cursor-ns-resize': { cursor: 'ns-resize' },
899
- 'cursor-nesw-resize': { cursor: 'nesw-resize' },
900
- 'cursor-nwse-resize': { cursor: 'nwse-resize' },
901
- 'cursor-zoom-in': { cursor: 'zoom-in' },
902
- 'cursor-zoom-out': { cursor: 'zoom-out' },
903
- }
904
-
905
- // Pointer events - direct raw class to CSS
906
- const POINTER_EVENTS_MAP: Record<string, Record<string, string>> = {
907
- 'pointer-events-none': { 'pointer-events': 'none' },
908
- 'pointer-events-auto': { 'pointer-events': 'auto' },
909
- }
910
-
911
- // Resize - direct raw class to CSS
912
- const RESIZE_MAP: Record<string, Record<string, string>> = {
913
- 'resize-none': { resize: 'none' },
914
- 'resize-y': { resize: 'vertical' },
915
- 'resize-x': { resize: 'horizontal' },
916
- 'resize': { resize: 'both' },
917
- }
918
-
919
- // User select - direct raw class to CSS
920
- const USER_SELECT_MAP: Record<string, Record<string, string>> = {
921
- 'select-none': { 'user-select': 'none' },
922
- 'select-text': { 'user-select': 'text' },
923
- 'select-all': { 'user-select': 'all' },
924
- 'select-auto': { 'user-select': 'auto' },
925
- }
926
-
927
- // Scroll behavior - direct raw class to CSS
928
- const SCROLL_BEHAVIOR_MAP: Record<string, Record<string, string>> = {
929
- 'scroll-auto': { 'scroll-behavior': 'auto' },
930
- 'scroll-smooth': { 'scroll-behavior': 'smooth' },
931
- }
932
-
933
- // Scroll snap type - direct raw class to CSS
934
- const SCROLL_SNAP_MAP: Record<string, Record<string, string>> = {
935
- 'snap-none': { 'scroll-snap-type': 'none' },
936
- 'snap-x': { 'scroll-snap-type': 'x var(--hw-scroll-snap-strictness)' },
937
- 'snap-y': { 'scroll-snap-type': 'y var(--hw-scroll-snap-strictness)' },
938
- 'snap-both': { 'scroll-snap-type': 'both var(--hw-scroll-snap-strictness)' },
939
- 'snap-mandatory': { '--hw-scroll-snap-strictness': 'mandatory' },
940
- 'snap-proximity': { '--hw-scroll-snap-strictness': 'proximity' },
941
- 'snap-start': { 'scroll-snap-align': 'start' },
942
- 'snap-end': { 'scroll-snap-align': 'end' },
943
- 'snap-center': { 'scroll-snap-align': 'center' },
944
- 'snap-align-none': { 'scroll-snap-align': 'none' },
945
- 'snap-normal': { 'scroll-snap-stop': 'normal' },
946
- 'snap-always': { 'scroll-snap-stop': 'always' },
947
- }
948
-
949
- // Touch action - direct raw class to CSS
950
- const TOUCH_ACTION_MAP: Record<string, Record<string, string>> = {
951
- 'touch-auto': { 'touch-action': 'auto' },
952
- 'touch-none': { 'touch-action': 'none' },
953
- 'touch-pan-x': { 'touch-action': 'pan-x' },
954
- 'touch-pan-left': { 'touch-action': 'pan-left' },
955
- 'touch-pan-right': { 'touch-action': 'pan-right' },
956
- 'touch-pan-y': { 'touch-action': 'pan-y' },
957
- 'touch-pan-up': { 'touch-action': 'pan-up' },
958
- 'touch-pan-down': { 'touch-action': 'pan-down' },
959
- 'touch-pinch-zoom': { 'touch-action': 'pinch-zoom' },
960
- 'touch-manipulation': { 'touch-action': 'manipulation' },
961
- }
962
-
963
- // Will change - direct raw class to CSS
964
- const WILL_CHANGE_MAP: Record<string, Record<string, string>> = {
965
- 'will-change-auto': { 'will-change': 'auto' },
966
- 'will-change-scroll': { 'will-change': 'scroll-position' },
967
- 'will-change-contents': { 'will-change': 'contents' },
968
- 'will-change-transform': { 'will-change': 'transform' },
969
- }
970
-
971
- // Appearance - direct raw class to CSS
972
- const APPEARANCE_MAP: Record<string, Record<string, string>> = {
973
- 'appearance-none': { appearance: 'none' },
974
- 'appearance-auto': { appearance: 'auto' },
975
- }
976
-
977
- // =============================================================================
978
- // TABLE UTILITIES - Fast path for table styling
979
- // =============================================================================
980
-
981
- // Border collapse - direct raw class to CSS
982
- const BORDER_COLLAPSE_MAP: Record<string, Record<string, string>> = {
983
- 'border-collapse': { 'border-collapse': 'collapse' },
984
- 'border-separate': { 'border-collapse': 'separate' },
985
- }
986
-
987
- // Table layout - direct raw class to CSS
988
- const TABLE_LAYOUT_MAP: Record<string, Record<string, string>> = {
989
- 'table-auto': { 'table-layout': 'auto' },
990
- 'table-fixed': { 'table-layout': 'fixed' },
991
- }
992
-
993
- // Caption side - direct raw class to CSS
994
- const CAPTION_SIDE_MAP: Record<string, Record<string, string>> = {
995
- 'caption-top': { 'caption-side': 'top' },
996
- 'caption-bottom': { 'caption-side': 'bottom' },
997
- }
998
-
999
- // =============================================================================
1000
- // ACCESSIBILITY UTILITIES - Fast path for a11y
1001
- // =============================================================================
1002
-
1003
- // Screen reader only - direct raw class to CSS
1004
- const SR_ONLY_MAP: Record<string, Record<string, string>> = {
1005
- 'sr-only': {
1006
- position: 'absolute',
1007
- width: '1px',
1008
- height: '1px',
1009
- padding: '0',
1010
- margin: '-1px',
1011
- overflow: 'hidden',
1012
- clip: 'rect(0, 0, 0, 0)',
1013
- 'white-space': 'nowrap',
1014
- 'border-width': '0',
1015
- },
1016
- 'not-sr-only': {
1017
- position: 'static',
1018
- width: 'auto',
1019
- height: 'auto',
1020
- padding: '0',
1021
- margin: '0',
1022
- overflow: 'visible',
1023
- clip: 'auto',
1024
- 'white-space': 'normal',
1025
- },
1026
- }
1027
-
1028
- // Forced color adjust - direct raw class to CSS
1029
- const FORCED_COLOR_MAP: Record<string, Record<string, string>> = {
1030
- 'forced-color-adjust-auto': { 'forced-color-adjust': 'auto' },
1031
- 'forced-color-adjust-none': { 'forced-color-adjust': 'none' },
1032
- }
1033
-
1034
- // =============================================================================
1035
- // SVG UTILITIES - Fast path for SVG
1036
- // =============================================================================
1037
-
1038
- // Fill - direct raw class to CSS
1039
- const FILL_MAP: Record<string, Record<string, string>> = {
1040
- 'fill-none': { fill: 'none' },
1041
- 'fill-current': { fill: 'currentColor' },
1042
- 'fill-inherit': { fill: 'inherit' },
1043
- }
1044
-
1045
- // Stroke - direct raw class to CSS
1046
- const STROKE_MAP: Record<string, Record<string, string>> = {
1047
- 'stroke-none': { stroke: 'none' },
1048
- 'stroke-current': { stroke: 'currentColor' },
1049
- 'stroke-inherit': { stroke: 'inherit' },
1050
- 'stroke-0': { 'stroke-width': '0' },
1051
- 'stroke-1': { 'stroke-width': '1' },
1052
- 'stroke-2': { 'stroke-width': '2' },
1053
- }
1054
-
1055
- // Stroke linecap - direct raw class to CSS
1056
- const STROKE_LINECAP_MAP: Record<string, Record<string, string>> = {
1057
- 'stroke-linecap-butt': { 'stroke-linecap': 'butt' },
1058
- 'stroke-linecap-round': { 'stroke-linecap': 'round' },
1059
- 'stroke-linecap-square': { 'stroke-linecap': 'square' },
1060
- }
1061
-
1062
- // Stroke linejoin - direct raw class to CSS
1063
- const STROKE_LINEJOIN_MAP: Record<string, Record<string, string>> = {
1064
- 'stroke-linejoin-miter': { 'stroke-linejoin': 'miter' },
1065
- 'stroke-linejoin-round': { 'stroke-linejoin': 'round' },
1066
- 'stroke-linejoin-bevel': { 'stroke-linejoin': 'bevel' },
1067
- }
1068
-
1069
- // =============================================================================
1070
- // FILTER UTILITIES - Fast path for filters
1071
- // =============================================================================
1072
-
1073
- // Blur - direct raw class to CSS
1074
- const BLUR_MAP: Record<string, Record<string, string>> = {
1075
- 'blur-none': { filter: 'blur(0)' },
1076
- 'blur-sm': { filter: 'blur(4px)' },
1077
- 'blur': { filter: 'blur(8px)' },
1078
- 'blur-md': { filter: 'blur(12px)' },
1079
- 'blur-lg': { filter: 'blur(16px)' },
1080
- 'blur-xl': { filter: 'blur(24px)' },
1081
- 'blur-2xl': { filter: 'blur(40px)' },
1082
- 'blur-3xl': { filter: 'blur(64px)' },
1083
- }
1084
-
1085
- // Backdrop blur - direct raw class to CSS
1086
- const BACKDROP_BLUR_MAP: Record<string, Record<string, string>> = {
1087
- 'backdrop-blur-none': { 'backdrop-filter': 'blur(0)' },
1088
- 'backdrop-blur-sm': { 'backdrop-filter': 'blur(4px)' },
1089
- 'backdrop-blur': { 'backdrop-filter': 'blur(8px)' },
1090
- 'backdrop-blur-md': { 'backdrop-filter': 'blur(12px)' },
1091
- 'backdrop-blur-lg': { 'backdrop-filter': 'blur(16px)' },
1092
- 'backdrop-blur-xl': { 'backdrop-filter': 'blur(24px)' },
1093
- 'backdrop-blur-2xl': { 'backdrop-filter': 'blur(40px)' },
1094
- 'backdrop-blur-3xl': { 'backdrop-filter': 'blur(64px)' },
1095
- }
1096
-
1097
- // Grayscale/invert/sepia - direct raw class to CSS
1098
- const FILTER_TOGGLE_MAP: Record<string, Record<string, string>> = {
1099
- 'grayscale-0': { filter: 'grayscale(0)' },
1100
- 'grayscale': { filter: 'grayscale(100%)' },
1101
- 'invert-0': { filter: 'invert(0)' },
1102
- 'invert': { filter: 'invert(100%)' },
1103
- 'sepia-0': { filter: 'sepia(0)' },
1104
- 'sepia': { filter: 'sepia(100%)' },
1105
- }
1106
-
1107
- // Backdrop grayscale/invert/sepia - direct raw class to CSS
1108
- const BACKDROP_FILTER_TOGGLE_MAP: Record<string, Record<string, string>> = {
1109
- 'backdrop-grayscale-0': { 'backdrop-filter': 'grayscale(0)' },
1110
- 'backdrop-grayscale': { 'backdrop-filter': 'grayscale(100%)' },
1111
- 'backdrop-invert-0': { 'backdrop-filter': 'invert(0)' },
1112
- 'backdrop-invert': { 'backdrop-filter': 'invert(100%)' },
1113
- 'backdrop-sepia-0': { 'backdrop-filter': 'sepia(0)' },
1114
- 'backdrop-sepia': { 'backdrop-filter': 'sepia(100%)' },
1115
- }
1116
-
1117
- // Drop shadow - direct raw class to CSS
1118
- const DROP_SHADOW_MAP: Record<string, Record<string, string>> = {
1119
- 'drop-shadow-sm': { filter: 'drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))' },
1120
- 'drop-shadow': { filter: 'drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))' },
1121
- 'drop-shadow-md': { filter: 'drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))' },
1122
- 'drop-shadow-lg': { filter: 'drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))' },
1123
- 'drop-shadow-xl': { filter: 'drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))' },
1124
- 'drop-shadow-2xl': { filter: 'drop-shadow(0 25px 25px rgb(0 0 0 / 0.15))' },
1125
- 'drop-shadow-none': { filter: 'drop-shadow(0 0 #0000)' },
1126
- }
1127
-
1128
- // Mix blend mode - direct raw class to CSS
1129
- const MIX_BLEND_MAP: Record<string, Record<string, string>> = {
1130
- 'mix-blend-normal': { 'mix-blend-mode': 'normal' },
1131
- 'mix-blend-multiply': { 'mix-blend-mode': 'multiply' },
1132
- 'mix-blend-screen': { 'mix-blend-mode': 'screen' },
1133
- 'mix-blend-overlay': { 'mix-blend-mode': 'overlay' },
1134
- 'mix-blend-darken': { 'mix-blend-mode': 'darken' },
1135
- 'mix-blend-lighten': { 'mix-blend-mode': 'lighten' },
1136
- 'mix-blend-color-dodge': { 'mix-blend-mode': 'color-dodge' },
1137
- 'mix-blend-color-burn': { 'mix-blend-mode': 'color-burn' },
1138
- 'mix-blend-hard-light': { 'mix-blend-mode': 'hard-light' },
1139
- 'mix-blend-soft-light': { 'mix-blend-mode': 'soft-light' },
1140
- 'mix-blend-difference': { 'mix-blend-mode': 'difference' },
1141
- 'mix-blend-exclusion': { 'mix-blend-mode': 'exclusion' },
1142
- 'mix-blend-hue': { 'mix-blend-mode': 'hue' },
1143
- 'mix-blend-saturation': { 'mix-blend-mode': 'saturation' },
1144
- 'mix-blend-color': { 'mix-blend-mode': 'color' },
1145
- 'mix-blend-luminosity': { 'mix-blend-mode': 'luminosity' },
1146
- 'mix-blend-plus-darker': { 'mix-blend-mode': 'plus-darker' },
1147
- 'mix-blend-plus-lighter': { 'mix-blend-mode': 'plus-lighter' },
1148
- }
1149
-
1150
- // Background blend mode - direct raw class to CSS
1151
- const BG_BLEND_MAP: Record<string, Record<string, string>> = {
1152
- 'bg-blend-normal': { 'background-blend-mode': 'normal' },
1153
- 'bg-blend-multiply': { 'background-blend-mode': 'multiply' },
1154
- 'bg-blend-screen': { 'background-blend-mode': 'screen' },
1155
- 'bg-blend-overlay': { 'background-blend-mode': 'overlay' },
1156
- 'bg-blend-darken': { 'background-blend-mode': 'darken' },
1157
- 'bg-blend-lighten': { 'background-blend-mode': 'lighten' },
1158
- 'bg-blend-color-dodge': { 'background-blend-mode': 'color-dodge' },
1159
- 'bg-blend-color-burn': { 'background-blend-mode': 'color-burn' },
1160
- 'bg-blend-hard-light': { 'background-blend-mode': 'hard-light' },
1161
- 'bg-blend-soft-light': { 'background-blend-mode': 'soft-light' },
1162
- 'bg-blend-difference': { 'background-blend-mode': 'difference' },
1163
- 'bg-blend-exclusion': { 'background-blend-mode': 'exclusion' },
1164
- 'bg-blend-hue': { 'background-blend-mode': 'hue' },
1165
- 'bg-blend-saturation': { 'background-blend-mode': 'saturation' },
1166
- 'bg-blend-color': { 'background-blend-mode': 'color' },
1167
- 'bg-blend-luminosity': { 'background-blend-mode': 'luminosity' },
1168
- }
1169
-
1170
- // Combined static map for raw class lookups (display, flex, transform, etc.)
1171
- const STATIC_UTILITY_MAP: Record<string, Record<string, string>> = {
1172
- ...DISPLAY_MAP,
1173
- ...FLEX_DIRECTION_MAP,
1174
- ...FLEX_WRAP_MAP,
1175
- ...FLEX_VALUES_MAP,
1176
- ...GROW_SHRINK_MAP,
1177
- ...BORDER_STYLE_MAP,
1178
- ...TRANSFORM_MAP,
1179
- ...TRANSITION_MAP,
1180
- ...TIMING_MAP,
1181
- ...ANIMATION_MAP,
1182
- ...RING_MAP,
1183
- ...SHADOW_MAP,
1184
- ...BORDER_RADIUS_MAP,
1185
- ...BORDER_WIDTH_MAP,
1186
- ...RING_OFFSET_MAP,
1187
- ...OPACITY_MAP,
1188
- // Typography
1189
- ...FONT_WEIGHT_MAP,
1190
- ...FONT_STYLE_MAP,
1191
- ...TEXT_TRANSFORM_MAP,
1192
- ...TEXT_DECORATION_MAP,
1193
- ...TEXT_DECORATION_STYLE_MAP,
1194
- ...TEXT_ALIGN_MAP,
1195
- ...TEXT_OVERFLOW_MAP,
1196
- ...TEXT_WRAP_MAP,
1197
- ...WHITESPACE_MAP,
1198
- ...WORD_BREAK_MAP,
1199
- ...VERTICAL_ALIGN_MAP,
1200
- ...LIST_STYLE_MAP,
1201
- ...HYPHENS_MAP,
1202
- // Layout
1203
- ...POSITION_MAP,
1204
- ...VISIBILITY_MAP,
1205
- ...FLOAT_MAP,
1206
- ...CLEAR_MAP,
1207
- ...ISOLATION_MAP,
1208
- ...OBJECT_FIT_MAP,
1209
- ...OBJECT_POSITION_MAP,
1210
- ...OVERFLOW_MAP,
1211
- ...OVERSCROLL_MAP,
1212
- ...Z_INDEX_MAP,
1213
- ...ASPECT_RATIO_MAP,
1214
- ...BOX_SIZING_MAP,
1215
- ...BOX_DECORATION_MAP,
1216
- // Background
1217
- ...BG_ATTACHMENT_MAP,
1218
- ...BG_CLIP_MAP,
1219
- ...BG_ORIGIN_MAP,
1220
- ...BG_POSITION_MAP,
1221
- ...BG_REPEAT_MAP,
1222
- ...BG_SIZE_MAP,
1223
- ...GRADIENT_MAP,
1224
- // Interactivity
1225
- ...CURSOR_MAP,
1226
- ...POINTER_EVENTS_MAP,
1227
- ...RESIZE_MAP,
1228
- ...USER_SELECT_MAP,
1229
- ...SCROLL_BEHAVIOR_MAP,
1230
- ...SCROLL_SNAP_MAP,
1231
- ...TOUCH_ACTION_MAP,
1232
- ...WILL_CHANGE_MAP,
1233
- ...APPEARANCE_MAP,
1234
- // Table
1235
- ...BORDER_COLLAPSE_MAP,
1236
- ...TABLE_LAYOUT_MAP,
1237
- ...CAPTION_SIDE_MAP,
1238
- // Accessibility
1239
- ...SR_ONLY_MAP,
1240
- ...FORCED_COLOR_MAP,
1241
- // SVG
1242
- ...FILL_MAP,
1243
- ...STROKE_MAP,
1244
- ...STROKE_LINECAP_MAP,
1245
- ...STROKE_LINEJOIN_MAP,
1246
- // Filters
1247
- ...BLUR_MAP,
1248
- ...BACKDROP_BLUR_MAP,
1249
- ...FILTER_TOGGLE_MAP,
1250
- ...BACKDROP_FILTER_TOGGLE_MAP,
1251
- ...DROP_SHADOW_MAP,
1252
- ...MIX_BLEND_MAP,
1253
- ...BG_BLEND_MAP,
1254
- }
1255
-
1256
- // Pre-computed variant selector map for O(1) lookup (shared across all instances)
1257
- const VARIANT_SELECTORS: Record<string, string> = {
1258
- // Pseudo-class variants
1259
- 'hover': ':hover',
1260
- 'focus': ':focus',
1261
- 'active': ':active',
1262
- 'disabled': ':disabled',
1263
- 'visited': ':visited',
1264
- 'checked': ':checked',
1265
- 'focus-within': ':focus-within',
1266
- 'focus-visible': ':focus-visible',
1267
- // Positional variants
1268
- 'first': ':first-child',
1269
- 'last': ':last-child',
1270
- 'odd': ':nth-child(odd)',
1271
- 'even': ':nth-child(even)',
1272
- 'first-of-type': ':first-of-type',
1273
- 'last-of-type': ':last-of-type',
1274
- // Pseudo-elements
1275
- 'before': '::before',
1276
- 'after': '::after',
1277
- 'marker': '::marker',
1278
- 'placeholder': '::placeholder',
1279
- 'selection': '::selection',
1280
- 'file': '::file-selector-button',
1281
- // Form state pseudo-classes
1282
- 'required': ':required',
1283
- 'valid': ':valid',
1284
- 'invalid': ':invalid',
1285
- 'read-only': ':read-only',
1286
- 'autofill': ':autofill',
1287
- // Additional state pseudo-classes
1288
- 'open': '[open]',
1289
- 'closed': ':not([open])',
1290
- 'empty': ':empty',
1291
- 'enabled': ':enabled',
1292
- 'only': ':only-child',
1293
- 'target': ':target',
1294
- 'indeterminate': ':indeterminate',
1295
- 'default': ':default',
1296
- 'optional': ':optional',
1297
- }
1298
-
1299
- // Pre-computed prefix variants (these modify the selector prefix, not suffix)
1300
- const PREFIX_VARIANTS: Record<string, string> = {
1301
- 'dark': '.dark ',
1302
- 'rtl': '[dir="rtl"] ',
1303
- 'ltr': '[dir="ltr"] ',
1304
- }
1305
-
1306
- /**
1307
- * Generates CSS rules from parsed utility classes
1308
- */
1309
- export class CSSGenerator {
1310
- private rules: Map<string, CSSRule[]> = new Map()
1311
- private classCache: Set<string> = new Set()
1312
- private blocklistRegexCache: RegExp[] = []
1313
- private blocklistExact: Set<string> = new Set()
1314
- private selectorCache: Map<string, string> = new Map()
1315
- private mediaQueryCache: Map<string, string | undefined> = new Map()
1316
- private ruleCache: Map<string, UtilityRule[]> = new Map()
1317
- private variantEnabled: Record<string, boolean>
1318
- private screenBreakpoints: Map<string, string>
1319
- // Cache for utility+value combinations that don't match any rule (negative cache)
1320
- private noMatchCache: Set<string> = new Set()
1321
- // Preserve extend colors for CSS variable generation (only custom colors, not defaults)
1322
- private extendColors: Record<string, string | Record<string, string>> | null = null
1323
-
1324
- constructor(private config: CrosswindConfig) {
1325
- // Merge preset themes into the main config theme
1326
- if (config.presets && config.presets.length > 0) {
1327
- for (const preset of config.presets) {
1328
- if (preset.theme) {
1329
- this.config.theme = deepMerge(this.config.theme, preset.theme)
1330
- }
1331
- }
1332
- }
1333
-
1334
- // Save extend colors before merging (for CSS variable generation)
1335
- if (config.theme.extend?.colors) {
1336
- this.extendColors = config.theme.extend.colors as Record<string, string | Record<string, string>>
1337
- }
1338
-
1339
- // Merge theme.extend into theme (allows users to add custom values without replacing defaults)
1340
- if (config.theme.extend) {
1341
- const { extend, ...baseTheme } = this.config.theme
1342
- if (extend) {
1343
- this.config.theme = deepMerge(baseTheme, extend) as typeof this.config.theme
1344
- }
1345
- }
1346
-
1347
- // Pre-compile blocklist patterns for performance
1348
- for (const pattern of this.config.blocklist) {
1349
- if (pattern.includes('*')) {
1350
- const regexPattern = pattern.replace(/\*/g, '.*')
1351
- this.blocklistRegexCache.push(new RegExp(`^${regexPattern}$`))
1352
- }
1353
- else {
1354
- this.blocklistExact.add(pattern)
1355
- }
1356
- }
1357
-
1358
- // Pre-cache variant enabled state for faster lookup
1359
- this.variantEnabled = this.config.variants as unknown as Record<string, boolean>
1360
-
1361
- // Pre-cache screen breakpoints as Map for faster lookup
1362
- this.screenBreakpoints = new Map(Object.entries(this.config.theme.screens))
1363
-
1364
- // Build rule lookup map for faster matching
1365
- this.buildRuleLookup()
1366
- }
1367
-
1368
- /**
1369
- * Build a prefix-based lookup map for rules
1370
- * This allows O(1) lookup instead of O(n) iteration
1371
- */
1372
- private buildRuleLookup(): void {
1373
- // Pre-processing done in constructor
1374
- // Rule caching happens during generation
1375
- }
1376
-
1377
- /**
1378
- * Generate CSS for a utility class
1379
- */
1380
- generate(className: string): void {
1381
- // Check cache for already processed classes
1382
- if (this.classCache.has(className)) {
1383
- return
1384
- }
1385
-
1386
- // Check shortcuts first (before marking as cached)
1387
- const shortcut = this.config.shortcuts[className]
1388
- if (shortcut) {
1389
- this.classCache.add(className)
1390
- const classes = Array.isArray(shortcut) ? shortcut : shortcut.split(/\s+/)
1391
- for (const cls of classes) {
1392
- this.generate(cls)
1393
- }
1394
- return
1395
- }
1396
-
1397
- this.classCache.add(className)
1398
-
1399
- // Check exact match blocklist first (O(1) Set lookup)
1400
- if (this.blocklistExact.size > 0 && this.blocklistExact.has(className)) {
1401
- return
1402
- }
1403
-
1404
- // Check if class is blocklisted (use pre-compiled regexes)
1405
- if (this.blocklistRegexCache.length > 0) {
1406
- for (let i = 0; i < this.blocklistRegexCache.length; i++) {
1407
- if (this.blocklistRegexCache[i].test(className)) {
1408
- return
1409
- }
1410
- }
1411
- }
1412
-
1413
- const parsed = parseClass(className)
1414
-
1415
- // ==========================================================================
1416
- // FAST PATH: Static utility map lookup (O(1))
1417
- // Handles ~80% of common utilities without any rule iteration
1418
- // ==========================================================================
1419
-
1420
- // Check static raw class map first (display, flex-direction, transform, transition, etc.)
1421
- const staticResult = STATIC_UTILITY_MAP[parsed.raw]
1422
- if (staticResult) {
1423
- this.addRule(parsed, staticResult)
1424
- return
1425
- }
1426
-
1427
- // Fast path for utility-based lookups
1428
- const utility = parsed.utility
1429
- const value = parsed.value
1430
-
1431
- // ==========================================================================
1432
- // SPACING UTILITIES - Most common, use pre-computed values
1433
- // ==========================================================================
1434
-
1435
- // Width: w-{size}
1436
- if (utility === 'w' && value) {
1437
- const sizeVal = SIZE_VALUES[value]
1438
- if (sizeVal) {
1439
- this.addRule(parsed, { width: sizeVal })
1440
- return
1441
- }
1442
- }
1443
-
1444
- // Height: h-{size}
1445
- if (utility === 'h' && value) {
1446
- const sizeVal = SIZE_VALUES[value]
1447
- if (sizeVal) {
1448
- this.addRule(parsed, { height: value === 'screen' ? '100vh' : sizeVal })
1449
- return
1450
- }
1451
- }
1452
-
1453
- // Padding: p-{size}
1454
- if (utility === 'p' && value) {
1455
- const spacingVal = SPACING_VALUES[value]
1456
- if (spacingVal) {
1457
- this.addRule(parsed, { padding: spacingVal })
1458
- return
1459
- }
1460
- }
1461
-
1462
- // Padding X: px-{size}
1463
- if (utility === 'px' && value) {
1464
- const spacingVal = SPACING_VALUES[value]
1465
- if (spacingVal) {
1466
- this.addRule(parsed, { 'padding-left': spacingVal, 'padding-right': spacingVal })
1467
- return
1468
- }
1469
- }
1470
-
1471
- // Padding Y: py-{size}
1472
- if (utility === 'py' && value) {
1473
- const spacingVal = SPACING_VALUES[value]
1474
- if (spacingVal) {
1475
- this.addRule(parsed, { 'padding-top': spacingVal, 'padding-bottom': spacingVal })
1476
- return
1477
- }
1478
- }
1479
-
1480
- // Padding sides: pt, pr, pb, pl
1481
- if ((utility === 'pt' || utility === 'pr' || utility === 'pb' || utility === 'pl') && value) {
1482
- const spacingVal = SPACING_VALUES[value]
1483
- if (spacingVal) {
1484
- const propMap: Record<string, string> = { pt: 'padding-top', pr: 'padding-right', pb: 'padding-bottom', pl: 'padding-left' }
1485
- this.addRule(parsed, { [propMap[utility]]: spacingVal })
1486
- return
1487
- }
1488
- }
1489
-
1490
- // Margin: m-{size}
1491
- if (utility === 'm' && value) {
1492
- const spacingVal = SPACING_VALUES[value]
1493
- if (spacingVal) {
1494
- this.addRule(parsed, { margin: spacingVal })
1495
- return
1496
- }
1497
- }
1498
-
1499
- // Margin X: mx-{size}
1500
- if (utility === 'mx' && value) {
1501
- const spacingVal = SPACING_VALUES[value]
1502
- if (spacingVal) {
1503
- this.addRule(parsed, { 'margin-left': spacingVal, 'margin-right': spacingVal })
1504
- return
1505
- }
1506
- }
1507
-
1508
- // Margin Y: my-{size}
1509
- if (utility === 'my' && value) {
1510
- const spacingVal = SPACING_VALUES[value]
1511
- if (spacingVal) {
1512
- this.addRule(parsed, { 'margin-top': spacingVal, 'margin-bottom': spacingVal })
1513
- return
1514
- }
1515
- }
1516
-
1517
- // Margin sides: mt, mr, mb, ml
1518
- if ((utility === 'mt' || utility === 'mr' || utility === 'mb' || utility === 'ml') && value) {
1519
- const spacingVal = SPACING_VALUES[value]
1520
- if (spacingVal) {
1521
- const propMap: Record<string, string> = { mt: 'margin-top', mr: 'margin-right', mb: 'margin-bottom', ml: 'margin-left' }
1522
- this.addRule(parsed, { [propMap[utility]]: spacingVal })
1523
- return
1524
- }
1525
- }
1526
-
1527
- // Position: top, right, bottom, left
1528
- if ((utility === 'top' || utility === 'right' || utility === 'bottom' || utility === 'left') && value) {
1529
- const spacingVal = SPACING_VALUES[value]
1530
- if (spacingVal) {
1531
- this.addRule(parsed, { [utility]: spacingVal })
1532
- return
1533
- }
1534
- }
1535
-
1536
- // Inset: inset-{size}
1537
- if (utility === 'inset' && value) {
1538
- const spacingVal = SPACING_VALUES[value]
1539
- if (spacingVal) {
1540
- this.addRule(parsed, { top: spacingVal, right: spacingVal, bottom: spacingVal, left: spacingVal })
1541
- return
1542
- }
1543
- }
1544
-
1545
- // Inset X/Y: inset-x-{size}, inset-y-{size}
1546
- if (utility === 'inset-x' && value) {
1547
- const spacingVal = SPACING_VALUES[value]
1548
- if (spacingVal) {
1549
- this.addRule(parsed, { left: spacingVal, right: spacingVal })
1550
- return
1551
- }
1552
- }
1553
- if (utility === 'inset-y' && value) {
1554
- const spacingVal = SPACING_VALUES[value]
1555
- if (spacingVal) {
1556
- this.addRule(parsed, { top: spacingVal, bottom: spacingVal })
1557
- return
1558
- }
1559
- }
1560
-
1561
- // ==========================================================================
1562
- // COLOR UTILITIES - Fast path using pre-computed color map
1563
- // ==========================================================================
1564
-
1565
- // Background color: bg-{color}-{shade}
1566
- if (utility === 'bg' && value) {
1567
- const colorVal = COMMON_COLORS[value]
1568
- if (colorVal) {
1569
- this.addRule(parsed, { 'background-color': colorVal })
1570
- return
1571
- }
1572
- }
1573
-
1574
- // Text color: text-{color}-{shade}
1575
- if (utility === 'text' && value) {
1576
- const colorVal = COMMON_COLORS[value]
1577
- if (colorVal) {
1578
- this.addRule(parsed, { color: colorVal })
1579
- return
1580
- }
1581
- }
1582
-
1583
- // Border color: border-{color}-{shade}
1584
- if (utility === 'border' && value) {
1585
- const colorVal = COMMON_COLORS[value]
1586
- if (colorVal) {
1587
- this.addRule(parsed, { 'border-color': colorVal })
1588
- return
1589
- }
1590
- }
1591
-
1592
- // Justify content: justify-{start|end|center|between|around|evenly}
1593
- if (utility === 'justify' && value) {
1594
- const justifyValue = JUSTIFY_CONTENT_VALUES[value]
1595
- if (justifyValue) {
1596
- this.addRule(parsed, { 'justify-content': justifyValue })
1597
- return
1598
- }
1599
- }
1600
-
1601
- // Align items: items-{start|end|center|baseline|stretch}
1602
- if (utility === 'items' && value) {
1603
- const itemsValue = ALIGN_ITEMS_VALUES[value]
1604
- if (itemsValue) {
1605
- this.addRule(parsed, { 'align-items': itemsValue })
1606
- return
1607
- }
1608
- }
1609
-
1610
- // Align content: content-{normal|center|start|end|between|around|evenly|baseline|stretch}
1611
- if (utility === 'content' && value) {
1612
- const contentValue = ALIGN_CONTENT_VALUES[value]
1613
- if (contentValue) {
1614
- this.addRule(parsed, { 'align-content': contentValue })
1615
- return
1616
- }
1617
- }
1618
-
1619
- // Align self: self-{auto|start|end|center|stretch|baseline}
1620
- if (utility === 'self' && value) {
1621
- const selfValue = ALIGN_SELF_VALUES[value]
1622
- if (selfValue) {
1623
- this.addRule(parsed, { 'align-self': selfValue })
1624
- return
1625
- }
1626
- }
1627
-
1628
- // Grid columns: grid-cols-{1-12|none|subgrid}
1629
- if (utility === 'grid-cols' && value) {
1630
- const colsValue = GRID_COLS_VALUES[value]
1631
- if (colsValue) {
1632
- this.addRule(parsed, { 'grid-template-columns': colsValue })
1633
- return
1634
- }
1635
- }
1636
-
1637
- // Grid rows: grid-rows-{1-6|none|subgrid}
1638
- if (utility === 'grid-rows' && value) {
1639
- const rowsValue = GRID_ROWS_VALUES[value]
1640
- if (rowsValue) {
1641
- this.addRule(parsed, { 'grid-template-rows': rowsValue })
1642
- return
1643
- }
1644
- }
1645
-
1646
- // Grid column span: col-{auto|span-1..12|span-full}
1647
- if (utility === 'col' && value) {
1648
- const colValue = GRID_COL_VALUES[value]
1649
- if (colValue) {
1650
- this.addRule(parsed, { 'grid-column': colValue })
1651
- return
1652
- }
1653
- }
1654
-
1655
- // Grid row span: row-{auto|span-1..6|span-full}
1656
- if (utility === 'row' && value) {
1657
- const rowValue = GRID_ROW_VALUES[value]
1658
- if (rowValue) {
1659
- this.addRule(parsed, { 'grid-row': rowValue })
1660
- return
1661
- }
1662
- }
1663
-
1664
- // Grid auto flow: grid-flow-{row|col|dense|row-dense|col-dense}
1665
- if (utility === 'grid-flow' && value) {
1666
- const flowValue = GRID_FLOW_VALUES[value]
1667
- if (flowValue) {
1668
- this.addRule(parsed, { 'grid-auto-flow': flowValue })
1669
- return
1670
- }
1671
- }
1672
-
1673
- // Auto cols: auto-cols-{auto|min|max|fr}
1674
- if (utility === 'auto-cols' && value) {
1675
- const autoColsValue = AUTO_COLS_ROWS_VALUES[value]
1676
- if (autoColsValue) {
1677
- this.addRule(parsed, { 'grid-auto-columns': autoColsValue })
1678
- return
1679
- }
1680
- }
1681
-
1682
- // Auto rows: auto-rows-{auto|min|max|fr}
1683
- if (utility === 'auto-rows' && value) {
1684
- const autoRowsValue = AUTO_COLS_ROWS_VALUES[value]
1685
- if (autoRowsValue) {
1686
- this.addRule(parsed, { 'grid-auto-rows': autoRowsValue })
1687
- return
1688
- }
1689
- }
1690
-
1691
- // Transform origin: origin-{center|top|top-right|...}
1692
- if (utility === 'origin' && value) {
1693
- const originValue = TRANSFORM_ORIGIN_VALUES[value]
1694
- if (originValue) {
1695
- this.addRule(parsed, { 'transform-origin': originValue })
1696
- return
1697
- }
1698
- }
1699
-
1700
- // Scale: scale-{0-150} -> transform: scale(value/100)
1701
- if (utility === 'scale' && value && !parsed.arbitrary) {
1702
- const scaleNum = Number(value) / 100
1703
- if (!Number.isNaN(scaleNum)) {
1704
- this.addRule(parsed, { transform: `scale(${scaleNum})` })
1705
- return
1706
- }
1707
- }
1708
- if (utility === 'scale-x' && value && !parsed.arbitrary) {
1709
- const scaleNum = Number(value) / 100
1710
- if (!Number.isNaN(scaleNum)) {
1711
- this.addRule(parsed, { transform: `scaleX(${scaleNum})` })
1712
- return
1713
- }
1714
- }
1715
- if (utility === 'scale-y' && value && !parsed.arbitrary) {
1716
- const scaleNum = Number(value) / 100
1717
- if (!Number.isNaN(scaleNum)) {
1718
- this.addRule(parsed, { transform: `scaleY(${scaleNum})` })
1719
- return
1720
- }
1721
- }
1722
-
1723
- // Rotate: rotate-{0-180} -> transform: rotate(Ndeg)
1724
- if (utility === 'rotate' && value && !parsed.arbitrary) {
1725
- this.addRule(parsed, { transform: `rotate(${value}deg)` })
1726
- return
1727
- }
1728
-
1729
- // Duration: duration-{75|100|150|200|300|500|700|1000}
1730
- if (utility === 'duration' && value) {
1731
- this.addRule(parsed, { 'transition-duration': `${value}ms` })
1732
- return
1733
- }
1734
-
1735
- // Delay: delay-{75|100|150|200|300|500|700|1000}
1736
- if (utility === 'delay' && value) {
1737
- this.addRule(parsed, { 'transition-delay': `${value}ms` })
1738
- return
1739
- }
1740
-
1741
- // Gap: gap-{spacing}
1742
- if (utility === 'gap' && value) {
1743
- const gapValue = this.config.theme.spacing[value] || value
1744
- this.addRule(parsed, { gap: gapValue })
1745
- return
1746
- }
1747
- if (utility === 'gap-x' && value) {
1748
- const gapValue = this.config.theme.spacing[value] || value
1749
- this.addRule(parsed, { 'column-gap': gapValue })
1750
- return
1751
- }
1752
- if (utility === 'gap-y' && value) {
1753
- const gapValue = this.config.theme.spacing[value] || value
1754
- this.addRule(parsed, { 'row-gap': gapValue })
1755
- return
1756
- }
1757
-
1758
- // Place content/items/self
1759
- if (utility === 'place' && value) {
1760
- if (value.startsWith('content-')) {
1761
- const placeValue = PLACE_CONTENT_VALUES[value.slice(8)]
1762
- if (placeValue) {
1763
- this.addRule(parsed, { 'place-content': placeValue })
1764
- return
1765
- }
1766
- }
1767
- if (value.startsWith('items-')) {
1768
- const placeValue = PLACE_CONTENT_VALUES[value.slice(6)]
1769
- if (placeValue) {
1770
- this.addRule(parsed, { 'place-items': placeValue })
1771
- return
1772
- }
1773
- }
1774
- if (value.startsWith('self-')) {
1775
- const placeValue = PLACE_CONTENT_VALUES[value.slice(5)]
1776
- if (placeValue) {
1777
- this.addRule(parsed, { 'place-self': placeValue })
1778
- return
1779
- }
1780
- }
1781
- }
1782
-
1783
- // ==========================================================================
1784
- // SLOW PATH: Rule iteration (only if fast path didn't match)
1785
- // ==========================================================================
1786
-
1787
- // Check no-match cache - if this utility+value combo was already determined to not match
1788
- // any rule, skip the expensive rule iteration (especially helpful for variant classes)
1789
- const utilityKey = `${parsed.utility}:${parsed.value || ''}`
1790
- if (this.noMatchCache.has(utilityKey)) {
1791
- return
1792
- }
1793
-
1794
- // Try custom rules from config first (allows overriding built-in rules)
1795
- if (this.config.rules.length > 0) {
1796
- for (const [pattern, handler] of this.config.rules) {
1797
- const match = className.match(pattern)
1798
- if (match) {
1799
- const properties = handler(match)
1800
- if (properties) {
1801
- this.addRule(parsed, properties)
1802
- return
1803
- }
1804
- }
1805
- }
1806
- }
1807
-
1808
- // Try built-in rules with optimized iteration
1809
- const rulesLength = builtInRules.length
1810
- for (let i = 0; i < rulesLength; i++) {
1811
- const result = builtInRules[i](parsed, this.config)
1812
- if (result) {
1813
- // Handle both old format (just properties) and new format (object with properties and childSelector/pseudoElement)
1814
- if ('properties' in result && typeof result.properties === 'object') {
1815
- this.addRule(parsed, result.properties, result.childSelector, result.pseudoElement)
1816
- }
1817
- else {
1818
- this.addRule(parsed, result as Record<string, string>)
1819
- }
1820
- return
1821
- }
1822
- }
1823
-
1824
- // No rule matched - cache this utility+value combo to skip future iterations
1825
- this.noMatchCache.add(utilityKey)
1826
- }
1827
-
1828
- /**
1829
- * Add a CSS rule with variants applied
1830
- */
1831
- private addRule(parsed: ParsedClass, properties: Record<string, string>, childSelector?: string, pseudoElement?: string): void {
1832
- // Use cached selector if available
1833
- const cacheKey = `${parsed.raw}${childSelector || ''}${pseudoElement || ''}`
1834
- let selector = this.selectorCache.get(cacheKey)
1835
- if (!selector) {
1836
- selector = this.buildSelector(parsed)
1837
- // Append pseudo-element directly (no space)
1838
- if (pseudoElement) {
1839
- selector += pseudoElement
1840
- }
1841
- // Append child selector if provided
1842
- if (childSelector) {
1843
- selector += ` ${childSelector}`
1844
- }
1845
- this.selectorCache.set(cacheKey, selector)
1846
- }
1847
-
1848
- const mediaQuery = this.getMediaQuery(parsed)
1849
-
1850
- // Apply !important modifier
1851
- if (parsed.important) {
1852
- for (const key in properties) {
1853
- properties[key] += ' !important'
1854
- }
1855
- }
1856
-
1857
- const key = mediaQuery || 'base'
1858
- if (!this.rules.has(key)) {
1859
- this.rules.set(key, [])
1860
- }
1861
-
1862
- this.rules.get(key)!.push({
1863
- selector,
1864
- properties,
1865
- mediaQuery,
1866
- childSelector,
1867
- })
1868
- }
1869
-
1870
- /**
1871
- * Build CSS selector with pseudo-classes and variants
1872
- * Optimized with pre-computed lookup maps for O(1) variant resolution
1873
- */
1874
- private buildSelector(parsed: ParsedClass): string {
1875
- let selector = `.${this.escapeSelector(parsed.raw)}`
1876
- let prefix = ''
1877
-
1878
- const variants = parsed.variants
1879
- const variantsLen = variants.length
1880
-
1881
- // Fast path: no variants
1882
- if (variantsLen === 0) {
1883
- return selector
1884
- }
1885
-
1886
- // Apply variants using pre-computed maps
1887
- for (let i = 0; i < variantsLen; i++) {
1888
- const variant = variants[i]
1889
-
1890
- // Try suffix selector lookup first (most common case)
1891
- const suffixSelector = VARIANT_SELECTORS[variant]
1892
- if (suffixSelector !== undefined) {
1893
- // Check if variant is enabled
1894
- if (this.variantEnabled[variant]) {
1895
- selector += suffixSelector
1896
- }
1897
- continue
1898
- }
1899
-
1900
- // Try prefix selector lookup (dark, rtl, ltr)
1901
- const prefixSelector = PREFIX_VARIANTS[variant]
1902
- if (prefixSelector !== undefined) {
1903
- if (this.variantEnabled[variant]) {
1904
- prefix = prefixSelector
1905
- }
1906
- continue
1907
- }
1908
-
1909
- // Handle group-* variants
1910
- if (variant.charCodeAt(0) === 103 && variant.startsWith('group-')) { // 'g' = 103
1911
- if (this.variantEnabled.group) {
1912
- const groupVariant = variant.slice(6)
1913
- prefix = `.group:${groupVariant} `
1914
- }
1915
- continue
1916
- }
1917
-
1918
- // Handle peer-* variants
1919
- if (variant.charCodeAt(0) === 112 && variant.startsWith('peer-')) { // 'p' = 112
1920
- if (this.variantEnabled.peer) {
1921
- const peerVariant = variant.slice(5)
1922
- prefix = `.peer:${peerVariant} ~ `
1923
- }
1924
- }
1925
- }
1926
-
1927
- return prefix + selector
1928
- }
1929
-
1930
- /**
1931
- * Get media query for responsive and media variants
1932
- * Optimized with pre-cached lookups and early returns
1933
- */
1934
- private getMediaQuery(parsed: ParsedClass): string | undefined {
1935
- const variants = parsed.variants
1936
- const variantsLen = variants.length
1937
-
1938
- // Fast path: no variants
1939
- if (variantsLen === 0) {
1940
- return undefined
1941
- }
1942
-
1943
- // Use cached media query if available
1944
- const cacheKey = variants.join(':')
1945
- const cached = this.mediaQueryCache.get(cacheKey)
1946
- if (cached !== undefined) {
1947
- return cached || undefined // Convert empty string to undefined
1948
- }
1949
-
1950
- let result: string | undefined
1951
-
1952
- for (let i = 0; i < variantsLen; i++) {
1953
- const variant = variants[i]
1954
- const firstChar = variant.charCodeAt(0)
1955
-
1956
- // Container queries (@sm, @md, @lg, etc.) - '@' = 64
1957
- if (firstChar === 64) {
1958
- const breakpointKey = variant.slice(1)
1959
- const breakpoint = this.screenBreakpoints.get(breakpointKey)
1960
- if (breakpoint) {
1961
- result = `@container (min-width: ${breakpoint})`
1962
- this.mediaQueryCache.set(cacheKey, result)
1963
- return result
1964
- }
1965
- continue
1966
- }
1967
-
1968
- // Responsive breakpoints - check if variant is a screen breakpoint
1969
- if (this.variantEnabled.responsive) {
1970
- const breakpoint = this.screenBreakpoints.get(variant)
1971
- if (breakpoint) {
1972
- result = `@media (min-width: ${breakpoint})`
1973
- this.mediaQueryCache.set(cacheKey, result)
1974
- return result
1975
- }
1976
- }
1977
-
1978
- // Media preference variants - use switch for common cases
1979
- switch (variant) {
1980
- case 'print':
1981
- if (this.variantEnabled.print) {
1982
- result = '@media print'
1983
- this.mediaQueryCache.set(cacheKey, result)
1984
- return result
1985
- }
1986
- break
1987
- case 'motion-safe':
1988
- if (this.variantEnabled['motion-safe']) {
1989
- result = '@media (prefers-reduced-motion: no-preference)'
1990
- this.mediaQueryCache.set(cacheKey, result)
1991
- return result
1992
- }
1993
- break
1994
- case 'motion-reduce':
1995
- if (this.variantEnabled['motion-reduce']) {
1996
- result = '@media (prefers-reduced-motion: reduce)'
1997
- this.mediaQueryCache.set(cacheKey, result)
1998
- return result
1999
- }
2000
- break
2001
- case 'contrast-more':
2002
- if (this.variantEnabled['contrast-more']) {
2003
- result = '@media (prefers-contrast: more)'
2004
- this.mediaQueryCache.set(cacheKey, result)
2005
- return result
2006
- }
2007
- break
2008
- case 'contrast-less':
2009
- if (this.variantEnabled['contrast-less']) {
2010
- result = '@media (prefers-contrast: less)'
2011
- this.mediaQueryCache.set(cacheKey, result)
2012
- return result
2013
- }
2014
- break
2015
- }
2016
- }
2017
-
2018
- this.mediaQueryCache.set(cacheKey, '') // Use empty string as "no result" marker
2019
- return undefined
2020
- }
2021
-
2022
- /**
2023
- * Escape special characters in class names for CSS selectors
2024
- * Optimized with charCode checks for common fast path
2025
- */
2026
- private escapeSelector(className: string): string {
2027
- // Fast path: check if string needs escaping at all
2028
- let needsEscape = false
2029
- for (let i = 0; i < className.length; i++) {
2030
- const c = className.charCodeAt(i)
2031
- // Check for : (58), . (46), / (47), @ (64), space (32), [ (91), ] (93)
2032
- if (c === 58 || c === 46 || c === 47 || c === 64 || c === 32 || c === 91 || c === 93) {
2033
- needsEscape = true
2034
- break
2035
- }
2036
- }
2037
- if (!needsEscape) {
2038
- return className
2039
- }
2040
- return className.replace(/[:./@ \[\]]/g, '\\$&')
2041
- }
2042
-
2043
- /**
2044
- * Generate final CSS output
2045
- */
2046
- toCSS(includePreflight = true, minify = false): string {
2047
- const parts: string[] = []
2048
-
2049
- // Add preflight CSS first (if requested)
2050
- if (includePreflight) {
2051
- for (const preflight of this.config.preflights) {
2052
- let preflightCSS = preflight.getCSS()
2053
- // Replace hardcoded font-family in preflight with theme's sans font
2054
- const sansFonts = this.config.theme?.fontFamily?.sans
2055
- if (sansFonts && Array.isArray(sansFonts)) {
2056
- const fontFamilyValue = sansFonts.join(', ')
2057
- // Replace the default ui-sans-serif stack in html/:host selector
2058
- preflightCSS = preflightCSS.replace(
2059
- /font-family:\s*ui-sans-serif[^;]+;/g,
2060
- `font-family: ${fontFamilyValue};`,
2061
- )
2062
- }
2063
- parts.push(minify ? preflightCSS.replace(/\s+/g, ' ').trim() : preflightCSS)
2064
- }
2065
- }
2066
-
2067
- // Generate CSS variables from theme colors if enabled
2068
- if (this.config.cssVariables) {
2069
- const vars = this.generateCSSVariables()
2070
- if (vars) {
2071
- parts.push(minify ? vars.replace(/\s+/g, ' ').trim() : vars)
2072
- }
2073
- }
2074
-
2075
- // Base rules (no media query)
2076
- const baseRules = this.rules.get('base') || []
2077
- if (baseRules.length > 0) {
2078
- parts.push(this.rulesToCSS(baseRules, minify))
2079
- }
2080
-
2081
- // Media query rules
2082
- for (const [key, rules] of this.rules.entries()) {
2083
- if (key !== 'base' && rules.length > 0) {
2084
- const mediaQuery = rules[0].mediaQuery!
2085
- const css = this.rulesToCSS(rules, minify)
2086
- parts.push(minify ? `${mediaQuery}{${css}}` : `${mediaQuery} {\n${css}\n}`)
2087
- }
2088
- }
2089
-
2090
- return minify ? parts.join('') : parts.join('\n\n')
2091
- }
2092
-
2093
- /**
2094
- * Convert rules to CSS string
2095
- */
2096
- private rulesToCSS(rules: CSSRule[], minify: boolean): string {
2097
- const grouped = this.groupRulesBySelector(rules)
2098
- const parts: string[] = []
2099
-
2100
- for (const [selector, properties] of grouped.entries()) {
2101
- const props = Array.from(properties.entries())
2102
- .map(([prop, value]) => minify ? `${prop}:${value}` : ` ${prop}: ${value};`)
2103
- .join(minify ? ';' : '\n')
2104
-
2105
- if (minify) {
2106
- parts.push(`${selector}{${props}}`)
2107
- }
2108
- else {
2109
- parts.push(`${selector} {\n${props}\n}`)
2110
- }
2111
- }
2112
-
2113
- return minify ? parts.join('') : parts.join('\n\n')
2114
- }
2115
-
2116
- /**
2117
- * Group rules by selector and merge properties
2118
- */
2119
- private groupRulesBySelector(rules: CSSRule[]): Map<string, Map<string, string>> {
2120
- const grouped = new Map<string, Map<string, string>>()
2121
-
2122
- for (const rule of rules) {
2123
- if (!grouped.has(rule.selector)) {
2124
- grouped.set(rule.selector, new Map())
2125
- }
2126
- const props = grouped.get(rule.selector)!
2127
- for (const [prop, value] of Object.entries(rule.properties)) {
2128
- props.set(prop, value)
2129
- }
2130
- }
2131
-
2132
- return grouped
2133
- }
2134
-
2135
- /**
2136
- * Generate :root CSS variables from theme colors
2137
- * Uses only extend colors (custom) if available, to avoid dumping 300+ default Tailwind colors.
2138
- * Flattens nested color objects: { monokai: { bg: '#2d2a2e' } } -> --monokai-bg: #2d2a2e
2139
- */
2140
- private generateCSSVariables(): string | null {
2141
- // Prefer extend colors (user's custom colors only), fall back to all theme colors
2142
- const colors = this.extendColors || this.config.theme.colors
2143
- if (!colors || Object.keys(colors).length === 0) return null
2144
-
2145
- const vars: string[] = []
2146
- for (const [name, value] of Object.entries(colors)) {
2147
- if (typeof value === 'string') {
2148
- vars.push(` --${name}: ${value};`)
2149
- }
2150
- else if (typeof value === 'object' && value !== null) {
2151
- for (const [shade, shadeValue] of Object.entries(value)) {
2152
- if (typeof shadeValue === 'string') {
2153
- vars.push(` --${name}-${shade}: ${shadeValue};`)
2154
- }
2155
- }
2156
- }
2157
- }
2158
-
2159
- if (vars.length === 0) return null
2160
- return `:root {\n${vars.join('\n')}\n}`
2161
- }
2162
-
2163
- /**
2164
- * Reset the generator state
2165
- */
2166
- reset(): void {
2167
- this.rules.clear()
2168
- this.classCache.clear()
2169
- this.selectorCache.clear()
2170
- this.mediaQueryCache.clear()
2171
- this.noMatchCache.clear()
2172
- }
2173
- }