@cwcss/crosswind 0.1.4

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 (64) hide show
  1. package/PLUGIN.md +235 -0
  2. package/benchmark/framework-comparison.bench.ts +850 -0
  3. package/bin/cli.ts +365 -0
  4. package/bin/crosswind +0 -0
  5. package/bin/headwind +0 -0
  6. package/build.ts +8 -0
  7. package/crosswind.config.ts +9 -0
  8. package/example/comprehensive.html +70 -0
  9. package/example/index.html +21 -0
  10. package/example/output.css +236 -0
  11. package/examples/plugin/README.md +112 -0
  12. package/examples/plugin/build.ts +32 -0
  13. package/examples/plugin/src/index.html +34 -0
  14. package/examples/plugin/src/index.ts +7 -0
  15. package/headwind +2 -0
  16. package/package.json +92 -0
  17. package/src/build.ts +101 -0
  18. package/src/config.ts +529 -0
  19. package/src/generator.ts +2173 -0
  20. package/src/index.ts +10 -0
  21. package/src/parser.ts +1471 -0
  22. package/src/plugin.ts +118 -0
  23. package/src/preflight-forms.ts +229 -0
  24. package/src/preflight.ts +388 -0
  25. package/src/rules-advanced.ts +477 -0
  26. package/src/rules-effects.ts +457 -0
  27. package/src/rules-forms.ts +103 -0
  28. package/src/rules-grid.ts +241 -0
  29. package/src/rules-interactivity.ts +525 -0
  30. package/src/rules-layout.ts +385 -0
  31. package/src/rules-transforms.ts +412 -0
  32. package/src/rules-typography.ts +486 -0
  33. package/src/rules.ts +805 -0
  34. package/src/scanner.ts +84 -0
  35. package/src/transformer-compile-class.ts +275 -0
  36. package/src/types.ts +197 -0
  37. package/test/advanced-features.test.ts +911 -0
  38. package/test/arbitrary.test.ts +396 -0
  39. package/test/attributify.test.ts +592 -0
  40. package/test/bracket-syntax.test.ts +1133 -0
  41. package/test/build.test.ts +99 -0
  42. package/test/colors.test.ts +934 -0
  43. package/test/flexbox.test.ts +669 -0
  44. package/test/generator.test.ts +597 -0
  45. package/test/grid.test.ts +584 -0
  46. package/test/layout.test.ts +404 -0
  47. package/test/modifiers.test.ts +417 -0
  48. package/test/parser.test.ts +564 -0
  49. package/test/performance-regression.test.ts +376 -0
  50. package/test/performance.test.ts +568 -0
  51. package/test/plugin.test.ts +160 -0
  52. package/test/scanner.test.ts +94 -0
  53. package/test/sizing.test.ts +481 -0
  54. package/test/spacing.test.ts +394 -0
  55. package/test/transformer-compile-class.test.ts +287 -0
  56. package/test/transforms.test.ts +448 -0
  57. package/test/typography.test.ts +632 -0
  58. package/test/variants-form-states.test.ts +225 -0
  59. package/test/variants-group-peer.test.ts +66 -0
  60. package/test/variants-media.test.ts +213 -0
  61. package/test/variants-positional.test.ts +58 -0
  62. package/test/variants-pseudo-elements.test.ts +47 -0
  63. package/test/variants-state.test.ts +62 -0
  64. package/tsconfig.json +18 -0
@@ -0,0 +1,385 @@
1
+ import type { UtilityRule } from './rules'
2
+
3
+ // Layout utilities
4
+
5
+ export const aspectRatioRule: UtilityRule = (parsed) => {
6
+ if (parsed.utility === 'aspect') {
7
+ const ratios: Record<string, string> = {
8
+ auto: 'auto',
9
+ square: '1 / 1',
10
+ video: '16 / 9',
11
+ }
12
+ return parsed.value ? { 'aspect-ratio': ratios[parsed.value] || parsed.value } : undefined
13
+ }
14
+ }
15
+
16
+ export const columnsRule: UtilityRule = (parsed, config) => {
17
+ if (parsed.utility === 'columns' && parsed.value) {
18
+ // Named column counts
19
+ const columnCounts: Record<string, string> = {
20
+ '1': '1',
21
+ '2': '2',
22
+ '3': '3',
23
+ '4': '4',
24
+ '5': '5',
25
+ '6': '6',
26
+ '7': '7',
27
+ '8': '8',
28
+ '9': '9',
29
+ '10': '10',
30
+ '11': '11',
31
+ '12': '12',
32
+ 'auto': 'auto',
33
+ }
34
+
35
+ // Named column widths (like Tailwind)
36
+ const columnWidths: Record<string, string> = {
37
+ '3xs': '16rem',
38
+ '2xs': '18rem',
39
+ 'xs': '20rem',
40
+ 'sm': '24rem',
41
+ 'md': '28rem',
42
+ 'lg': '32rem',
43
+ 'xl': '36rem',
44
+ '2xl': '42rem',
45
+ '3xl': '48rem',
46
+ '4xl': '56rem',
47
+ '5xl': '64rem',
48
+ '6xl': '72rem',
49
+ '7xl': '80rem',
50
+ }
51
+
52
+ // Check column counts first
53
+ if (columnCounts[parsed.value]) {
54
+ return { columns: columnCounts[parsed.value] }
55
+ }
56
+
57
+ // Check named widths
58
+ if (columnWidths[parsed.value]) {
59
+ return { columns: columnWidths[parsed.value] }
60
+ }
61
+
62
+ // Check spacing config
63
+ if (config.theme.spacing[parsed.value]) {
64
+ return { columns: config.theme.spacing[parsed.value] }
65
+ }
66
+
67
+ // Arbitrary value support
68
+ return { columns: parsed.value }
69
+ }
70
+ }
71
+
72
+ // Column fill
73
+ export const columnFillRule: UtilityRule = (parsed) => {
74
+ const values: Record<string, string> = {
75
+ 'column-fill-auto': 'auto',
76
+ 'column-fill-balance': 'balance',
77
+ 'column-fill-balance-all': 'balance-all',
78
+ }
79
+ return values[parsed.raw] ? { 'column-fill': values[parsed.raw] } : undefined
80
+ }
81
+
82
+ // Column gap (different from grid gap)
83
+ export const columnGapRule: UtilityRule = (parsed, config) => {
84
+ if (parsed.utility === 'column-gap' && parsed.value) {
85
+ return { 'column-gap': config.theme.spacing[parsed.value] || parsed.value }
86
+ }
87
+ }
88
+
89
+ // Column rule (border between columns)
90
+ export const columnRuleRule: UtilityRule = (parsed, config) => {
91
+ // column-rule-width
92
+ if (parsed.utility === 'column-rule' && parsed.value) {
93
+ const widths: Record<string, string> = {
94
+ '0': '0px',
95
+ '1': '1px',
96
+ '2': '2px',
97
+ '4': '4px',
98
+ '8': '8px',
99
+ }
100
+ if (widths[parsed.value]) {
101
+ return { 'column-rule-width': widths[parsed.value] } as Record<string, string>
102
+ }
103
+
104
+ // Check for colors
105
+ const parts = parsed.value.split('-')
106
+ if (parts.length === 2) {
107
+ const [colorName, shade] = parts
108
+ const colorValue = config.theme.colors[colorName]
109
+ if (typeof colorValue === 'object' && colorValue[shade]) {
110
+ return { 'column-rule-color': colorValue[shade] } as Record<string, string>
111
+ }
112
+ }
113
+
114
+ // Direct color
115
+ const directColor = config.theme.colors[parsed.value]
116
+ if (typeof directColor === 'string') {
117
+ return { 'column-rule-color': directColor } as Record<string, string>
118
+ }
119
+
120
+ // Style
121
+ const styles: Record<string, string> = {
122
+ solid: 'solid',
123
+ dashed: 'dashed',
124
+ dotted: 'dotted',
125
+ double: 'double',
126
+ hidden: 'hidden',
127
+ none: 'none',
128
+ }
129
+ if (styles[parsed.value]) {
130
+ return { 'column-rule-style': styles[parsed.value] } as Record<string, string>
131
+ }
132
+ }
133
+ }
134
+
135
+ // Column span
136
+ export const columnSpanRule: UtilityRule = (parsed) => {
137
+ const values: Record<string, string> = {
138
+ 'column-span-all': 'all',
139
+ 'column-span-none': 'none',
140
+ }
141
+ return values[parsed.raw] ? { 'column-span': values[parsed.raw] } : undefined
142
+ }
143
+
144
+ export const breakRule: UtilityRule = (parsed) => {
145
+ const breaks: Record<string, Record<string, string>> = {
146
+ 'break-before-auto': { 'break-before': 'auto' },
147
+ 'break-before-avoid': { 'break-before': 'avoid' },
148
+ 'break-before-all': { 'break-before': 'all' },
149
+ 'break-before-avoid-page': { 'break-before': 'avoid-page' },
150
+ 'break-before-page': { 'break-before': 'page' },
151
+ 'break-after-auto': { 'break-after': 'auto' },
152
+ 'break-after-avoid': { 'break-after': 'avoid' },
153
+ 'break-after-all': { 'break-after': 'all' },
154
+ 'break-after-avoid-page': { 'break-after': 'avoid-page' },
155
+ 'break-after-page': { 'break-after': 'page' },
156
+ 'break-inside-auto': { 'break-inside': 'auto' },
157
+ 'break-inside-avoid': { 'break-inside': 'avoid' },
158
+ 'break-inside-avoid-page': { 'break-inside': 'avoid-page' },
159
+ 'break-inside-avoid-column': { 'break-inside': 'avoid-column' },
160
+ }
161
+ return breaks[parsed.raw]
162
+ }
163
+
164
+ export const boxDecorationRule: UtilityRule = (parsed) => {
165
+ const values: Record<string, string> = {
166
+ 'box-decoration-clone': 'clone',
167
+ 'box-decoration-slice': 'slice',
168
+ }
169
+ return values[parsed.raw] ? { 'box-decoration-break': values[parsed.raw] } : undefined
170
+ }
171
+
172
+ export const boxSizingRule: UtilityRule = (parsed) => {
173
+ const values: Record<string, string> = {
174
+ 'box-border': 'border-box',
175
+ 'box-content': 'content-box',
176
+ }
177
+ return values[parsed.raw] ? { 'box-sizing': values[parsed.raw] } : undefined
178
+ }
179
+
180
+ export const floatRule: UtilityRule = (parsed) => {
181
+ const floats: Record<string, string> = {
182
+ 'float-start': 'inline-start',
183
+ 'float-end': 'inline-end',
184
+ 'float-right': 'right',
185
+ 'float-left': 'left',
186
+ 'float-none': 'none',
187
+ }
188
+ return floats[parsed.raw] ? { float: floats[parsed.raw] } : undefined
189
+ }
190
+
191
+ export const clearRule: UtilityRule = (parsed) => {
192
+ const clears: Record<string, string> = {
193
+ 'clear-start': 'inline-start',
194
+ 'clear-end': 'inline-end',
195
+ 'clear-left': 'left',
196
+ 'clear-right': 'right',
197
+ 'clear-both': 'both',
198
+ 'clear-none': 'none',
199
+ }
200
+ return clears[parsed.raw] ? { clear: clears[parsed.raw] } : undefined
201
+ }
202
+
203
+ export const isolationRule: UtilityRule = (parsed) => {
204
+ const values: Record<string, string> = {
205
+ 'isolate': 'isolate',
206
+ 'isolation-auto': 'auto',
207
+ }
208
+ return values[parsed.raw] ? { isolation: values[parsed.raw] } : undefined
209
+ }
210
+
211
+ export const objectFitRule: UtilityRule = (parsed) => {
212
+ const fits: Record<string, string> = {
213
+ 'object-contain': 'contain',
214
+ 'object-cover': 'cover',
215
+ 'object-fill': 'fill',
216
+ 'object-none': 'none',
217
+ 'object-scale-down': 'scale-down',
218
+ }
219
+ return fits[parsed.raw] ? { 'object-fit': fits[parsed.raw] } : undefined
220
+ }
221
+
222
+ export const objectPositionRule: UtilityRule = (parsed) => {
223
+ const positions: Record<string, string> = {
224
+ 'object-bottom': 'bottom',
225
+ 'object-center': 'center',
226
+ 'object-left': 'left',
227
+ 'object-left-bottom': 'left bottom',
228
+ 'object-left-top': 'left top',
229
+ 'object-right': 'right',
230
+ 'object-right-bottom': 'right bottom',
231
+ 'object-right-top': 'right top',
232
+ 'object-top': 'top',
233
+ }
234
+ return positions[parsed.raw] ? { 'object-position': positions[parsed.raw] } : undefined
235
+ }
236
+
237
+ export const overflowRule: UtilityRule = (parsed) => {
238
+ if (parsed.utility === 'overflow') {
239
+ const values = ['auto', 'hidden', 'clip', 'visible', 'scroll']
240
+ if (parsed.value && values.includes(parsed.value)) {
241
+ return { overflow: parsed.value }
242
+ }
243
+ }
244
+ if (parsed.utility === 'overflow-x' || parsed.utility === 'overflow-y') {
245
+ const values = ['auto', 'hidden', 'clip', 'visible', 'scroll']
246
+ if (parsed.value && values.includes(parsed.value)) {
247
+ return { [parsed.utility]: parsed.value }
248
+ }
249
+ }
250
+ }
251
+
252
+ export const overscrollRule: UtilityRule = (parsed) => {
253
+ const behaviors: Record<string, string> = {
254
+ 'overscroll-auto': 'auto',
255
+ 'overscroll-contain': 'contain',
256
+ 'overscroll-none': 'none',
257
+ 'overscroll-x-auto': 'auto',
258
+ 'overscroll-x-contain': 'contain',
259
+ 'overscroll-x-none': 'none',
260
+ 'overscroll-y-auto': 'auto',
261
+ 'overscroll-y-contain': 'contain',
262
+ 'overscroll-y-none': 'none',
263
+ }
264
+ const prop = parsed.raw.startsWith('overscroll-x')
265
+ ? 'overscroll-behavior-x'
266
+ : parsed.raw.startsWith('overscroll-y')
267
+ ? 'overscroll-behavior-y'
268
+ : 'overscroll-behavior'
269
+ return behaviors[parsed.raw] ? { [prop]: behaviors[parsed.raw] } : undefined
270
+ }
271
+
272
+ export const positionRule: UtilityRule = (parsed) => {
273
+ const positions = ['static', 'fixed', 'absolute', 'relative', 'sticky']
274
+ if (positions.includes(parsed.utility)) {
275
+ return { position: parsed.utility }
276
+ }
277
+ }
278
+
279
+ export const insetRule: UtilityRule = (parsed, config) => {
280
+ const directions: Record<string, string[]> = {
281
+ 'inset': ['top', 'right', 'bottom', 'left'],
282
+ 'inset-x': ['left', 'right'],
283
+ 'inset-y': ['top', 'bottom'],
284
+ 'top': ['top'],
285
+ 'right': ['right'],
286
+ 'bottom': ['bottom'],
287
+ 'left': ['left'],
288
+ // Logical inset (for RTL support)
289
+ 'start': ['inset-inline-start'],
290
+ 'end': ['inset-inline-end'],
291
+ }
292
+
293
+ const props = directions[parsed.utility]
294
+ if (!props || !parsed.value)
295
+ return undefined
296
+
297
+ // Helper to resolve inset value (handles fractions, spacing, keywords)
298
+ const resolveInsetValue = (val: string): string => {
299
+ // Handle fractions: 1/2 -> 50%, 1/3 -> 33.333333%, etc.
300
+ if (val.includes('/')) {
301
+ const [num, denom] = val.split('/').map(Number)
302
+ if (!Number.isNaN(num) && !Number.isNaN(denom) && denom !== 0) {
303
+ return `${(num / denom) * 100}%`
304
+ }
305
+ }
306
+ // Handle special keywords
307
+ if (val === 'full')
308
+ return '100%'
309
+ if (val === 'auto')
310
+ return 'auto'
311
+ // Check spacing config, then fall back to raw value
312
+ return config.theme.spacing[val] || val
313
+ }
314
+
315
+ // Handle negative values
316
+ let value: string
317
+ if (parsed.value.startsWith('-')) {
318
+ const positiveValue = parsed.value.slice(1)
319
+ const resolved = resolveInsetValue(positiveValue)
320
+ // For percentage values, negate properly
321
+ if (resolved.endsWith('%')) {
322
+ const numericPart = Number.parseFloat(resolved)
323
+ value = `${-numericPart}%`
324
+ }
325
+ else {
326
+ value = resolved.startsWith('-') ? resolved : `-${resolved}`
327
+ }
328
+ }
329
+ else {
330
+ value = resolveInsetValue(parsed.value)
331
+ }
332
+
333
+ const result: Record<string, string> = {}
334
+ for (const prop of props) {
335
+ result[prop] = value
336
+ }
337
+ return result
338
+ }
339
+
340
+ export const visibilityRule: UtilityRule = (parsed) => {
341
+ const values: Record<string, string> = {
342
+ visible: 'visible',
343
+ invisible: 'hidden',
344
+ collapse: 'collapse',
345
+ }
346
+ return values[parsed.utility] ? { visibility: values[parsed.utility] } : undefined
347
+ }
348
+
349
+ export const zIndexRule: UtilityRule = (parsed) => {
350
+ if (parsed.utility === 'z' && parsed.value) {
351
+ const zIndexMap: Record<string, string> = {
352
+ 0: '0',
353
+ 10: '10',
354
+ 20: '20',
355
+ 30: '30',
356
+ 40: '40',
357
+ 50: '50',
358
+ auto: 'auto',
359
+ }
360
+ return { 'z-index': zIndexMap[parsed.value] || parsed.value }
361
+ }
362
+ }
363
+
364
+ export const layoutRules: UtilityRule[] = [
365
+ aspectRatioRule,
366
+ columnsRule,
367
+ columnFillRule,
368
+ columnGapRule,
369
+ columnRuleRule,
370
+ columnSpanRule,
371
+ breakRule,
372
+ boxDecorationRule,
373
+ boxSizingRule,
374
+ floatRule,
375
+ clearRule,
376
+ isolationRule,
377
+ objectFitRule,
378
+ objectPositionRule,
379
+ overflowRule,
380
+ overscrollRule,
381
+ positionRule,
382
+ insetRule,
383
+ visibilityRule,
384
+ zIndexRule,
385
+ ]