twirlwind 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -124
- package/dist/index.d.mts +16 -29
- package/dist/index.mjs +15 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,100 +10,58 @@ Converts style objects, CSS declaration strings, and `CSSStyleDeclaration` value
|
|
|
10
10
|
npm install twirlwind
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Usage
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import { twirl } from 'twirlwind'
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
justifyContent: 'center',
|
|
21
|
-
padding: '16px 8px',
|
|
22
|
-
backgroundColor: 'oklch(62.3% 0.214 259.815)'
|
|
23
|
-
})
|
|
24
|
-
// → "flex py-4 px-2 justify-center bg-blue-500"
|
|
18
|
+
twirl({ display: 'flex', padding: '16px 8px', color: '#ef4444' })
|
|
19
|
+
// → "flex py-4 px-2 text-red-500"
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### `styleToTailwind(input, options?)`
|
|
22
|
+
### Inputs
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
`twirl()` accepts any of these and returns a class string:
|
|
32
25
|
|
|
33
26
|
```ts
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const result = styleToTailwind({ display: 'flex', width: '37px' })
|
|
37
|
-
|
|
38
|
-
result.className // "flex w-[37px]"
|
|
39
|
-
result.classes // ["flex", "w-[37px]"]
|
|
40
|
-
result.exact // ConvertedDeclaration[] — exact utility matches
|
|
41
|
-
result.arbitrary // ConvertedDeclaration[] — arbitrary value/property fallbacks
|
|
42
|
-
result.unmatched // CssDeclaration[] — declarations that couldn't be converted
|
|
43
|
-
result.warnings // ConversionWarning[]
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### `styleToClassName(input, options?)`
|
|
27
|
+
// Style object
|
|
28
|
+
twirl({ backgroundColor: 'white', fontSize: '16px' })
|
|
47
29
|
|
|
48
|
-
|
|
30
|
+
// CSS string
|
|
31
|
+
twirl('display: flex; padding: 16px')
|
|
49
32
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// → "p-4 text-white"
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### `styleToClasses(input, options?)`
|
|
56
|
-
|
|
57
|
-
Returns an array of class strings.
|
|
33
|
+
// CSSStyleDeclaration (browser)
|
|
34
|
+
twirl(element.style)
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// → ["my-2", "mx-4"]
|
|
36
|
+
// Computed styles
|
|
37
|
+
twirl(getComputedStyle(element))
|
|
62
38
|
```
|
|
63
39
|
|
|
64
|
-
###
|
|
40
|
+
### Detailed result
|
|
65
41
|
|
|
66
|
-
|
|
42
|
+
Use `twirl.convert()` when you need metadata:
|
|
67
43
|
|
|
68
44
|
```ts
|
|
69
|
-
|
|
70
|
-
// → { className: "flex p-2 gap-4", ... }
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Input formats
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
// Style object (camelCase or kebab-case)
|
|
77
|
-
styleToClassName({ backgroundColor: 'white', 'font-size': '16px' })
|
|
78
|
-
|
|
79
|
-
// CSS declaration string
|
|
80
|
-
styleToClassName('display: flex; padding: 16px')
|
|
45
|
+
const result = twirl.convert({ display: 'flex', width: '37px' })
|
|
81
46
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
new Map([
|
|
88
|
-
['display', 'flex'],
|
|
89
|
-
['padding', '16px']
|
|
90
|
-
])
|
|
91
|
-
)
|
|
47
|
+
result.className // "flex w-[37px]"
|
|
48
|
+
result.classes // ["flex", "w-[37px]"]
|
|
49
|
+
result.exact // exact utility matches
|
|
50
|
+
result.arbitrary // arbitrary value/property fallbacks
|
|
51
|
+
result.unmatched // declarations that couldn't be converted
|
|
92
52
|
```
|
|
93
53
|
|
|
94
54
|
## Features
|
|
95
55
|
|
|
96
56
|
### Color matching
|
|
97
57
|
|
|
98
|
-
Matches
|
|
58
|
+
Matches across formats — OKLCH, hex, `rgb()`, keywords, opacity modifiers.
|
|
99
59
|
|
|
100
60
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
styleToClassName({ color: 'inherit' }) // "text-inherit"
|
|
106
|
-
styleToClassName({ color: 'currentColor' }) // "text-current"
|
|
61
|
+
twirl({ color: '#ef4444' }) // "text-red-500"
|
|
62
|
+
twirl({ color: 'rgb(59 130 246)' }) // "text-blue-500"
|
|
63
|
+
twirl({ color: 'oklch(62.3% 0.214 259.815 / 50%)' }) // "text-blue-500/50"
|
|
64
|
+
twirl({ color: 'currentColor' }) // "text-current"
|
|
107
65
|
```
|
|
108
66
|
|
|
109
67
|
### Shorthand expansion
|
|
@@ -111,49 +69,39 @@ styleToClassName({ color: 'currentColor' }) // "text-current"
|
|
|
111
69
|
CSS shorthands decompose into Tailwind longhands.
|
|
112
70
|
|
|
113
71
|
```ts
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
styleToClassName({ font: 'bold 16px/1.5 sans-serif' })
|
|
118
|
-
// → "font-bold text-base leading-normal font-sans"
|
|
119
|
-
|
|
120
|
-
styleToClassName({ background: 'white center no-repeat' })
|
|
121
|
-
// → "bg-white bg-center bg-no-repeat"
|
|
122
|
-
|
|
123
|
-
styleToClassName({ transition: 'all 200ms ease-in' })
|
|
124
|
-
// → "transition-all duration-200 ease-in"
|
|
72
|
+
twirl({ border: '2px solid #ef4444' }) // "border-2 border-red-500 border-solid"
|
|
73
|
+
twirl({ font: 'bold 16px/1.5 sans-serif' }) // "font-bold text-base leading-normal font-sans"
|
|
74
|
+
twirl({ background: 'white center no-repeat' }) // "bg-white bg-center bg-no-repeat"
|
|
125
75
|
```
|
|
126
76
|
|
|
127
|
-
### Multi-value
|
|
77
|
+
### Multi-value parsing
|
|
128
78
|
|
|
129
|
-
Compound `transform` and `filter` declarations decompose into individual
|
|
79
|
+
Compound `transform` and `filter` declarations decompose into individual classes.
|
|
130
80
|
|
|
131
81
|
```ts
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
styleToClassName({ filter: 'blur(8px) brightness(0.75)' })
|
|
136
|
-
// → "blur brightness-75"
|
|
82
|
+
twirl({ transform: 'translateX(8px) rotate(45deg)' }) // "rotate-45 translate-x-2"
|
|
83
|
+
twirl({ filter: 'blur(8px) brightness(0.75)' }) // "blur brightness-75"
|
|
84
|
+
twirl({ scrollSnapType: 'x mandatory' }) // "snap-x snap-mandatory"
|
|
137
85
|
```
|
|
138
86
|
|
|
139
87
|
### Compression
|
|
140
88
|
|
|
141
|
-
Expanded longhands compress to shorthand utilities
|
|
89
|
+
Expanded longhands compress to shorthand utilities.
|
|
142
90
|
|
|
143
91
|
```ts
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
92
|
+
twirl({ margin: '8px' }) // "m-2"
|
|
93
|
+
twirl({ inset: '0' }) // "inset-0"
|
|
94
|
+
twirl({ padding: '8px 16px' }) // "py-2 px-4"
|
|
95
|
+
twirl({ borderRadius: '8px' }) // "rounded-lg"
|
|
96
|
+
twirl({ gap: '12px 12px' }) // "gap-3"
|
|
149
97
|
```
|
|
150
98
|
|
|
151
99
|
### Variants
|
|
152
100
|
|
|
153
|
-
Nested objects map to Tailwind variants
|
|
101
|
+
Nested objects map to Tailwind variants.
|
|
154
102
|
|
|
155
103
|
```ts
|
|
156
|
-
|
|
104
|
+
twirl({
|
|
157
105
|
color: 'white',
|
|
158
106
|
':hover': { color: '#3b82f6' },
|
|
159
107
|
'@media (min-width: 768px)': { display: 'grid' },
|
|
@@ -163,48 +111,25 @@ styleToClassName({
|
|
|
163
111
|
// → "text-white hover:text-blue-500 md:grid dark:bg-black @lg:flex"
|
|
164
112
|
```
|
|
165
113
|
|
|
166
|
-
### Scroll snap decomposition
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
styleToClassName({ scrollSnapType: 'x mandatory' })
|
|
170
|
-
// → "snap-x snap-mandatory"
|
|
171
|
-
```
|
|
172
|
-
|
|
173
114
|
### Arbitrary fallback
|
|
174
115
|
|
|
175
|
-
Every CSS property produces valid output.
|
|
116
|
+
Every CSS property produces valid output.
|
|
176
117
|
|
|
177
118
|
```ts
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
styleToClassName({ width: '37px' })
|
|
182
|
-
// → "w-[37px]"
|
|
119
|
+
twirl({ scrollTimelineName: '--main' }) // "[scroll-timeline-name:--main]"
|
|
120
|
+
twirl({ width: '37px' }) // "w-[37px]"
|
|
183
121
|
```
|
|
184
122
|
|
|
185
123
|
## Options
|
|
186
124
|
|
|
187
125
|
```ts
|
|
188
|
-
|
|
189
|
-
// Allow arbitrary value utilities like w-[37px]
|
|
126
|
+
twirl(input, {
|
|
190
127
|
allowArbitraryValues: true, // default: true
|
|
191
|
-
|
|
192
|
-
// Allow arbitrary property fallbacks like [scroll-timeline-name:--x]
|
|
193
128
|
allowArbitraryProperties: true, // default: true
|
|
194
|
-
|
|
195
|
-
// Compress matching longhands to shorthands
|
|
196
129
|
compression: 'safe', // "none" | "safe" | "aggressive"
|
|
197
|
-
|
|
198
|
-
// Sort output classes
|
|
199
130
|
sort: 'grouped', // "input" | "tailwind" | "grouped"
|
|
200
|
-
|
|
201
|
-
// Color matching strategy
|
|
202
131
|
colorMatch: 'exact', // "exact" | "nearest" | "none"
|
|
203
|
-
|
|
204
|
-
// Spacing token multiplier mode
|
|
205
132
|
numericMultipliers: 'integer', // "all" | "integer" | "never"
|
|
206
|
-
|
|
207
|
-
// Custom theme colors/spacing
|
|
208
133
|
theme: {
|
|
209
134
|
colors: { brand: '#ff6600' },
|
|
210
135
|
spacing: { '18': '4.5rem' }
|
|
@@ -215,9 +140,9 @@ styleToTailwind(input, {
|
|
|
215
140
|
## How it works
|
|
216
141
|
|
|
217
142
|
1. **Normalize** — camelCase → kebab-case, numeric → px, vendor prefixes, `!important`
|
|
218
|
-
2. **Expand
|
|
143
|
+
2. **Expand** — `margin`, `border`, `font`, `background`, `transition`, `overflow`, `gap`, etc.
|
|
219
144
|
3. **Convert** — exact utility → value alias → spacing token → color match → arbitrary value → arbitrary property
|
|
220
|
-
4. **Compress** — merge
|
|
145
|
+
4. **Compress** — merge longhands back to shorthand utilities
|
|
221
146
|
5. **Sort** — deterministic output ordering
|
|
222
147
|
|
|
223
148
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -4,55 +4,42 @@ interface StyleObject {
|
|
|
4
4
|
[property: string]: StylePrimitive | StyleObject;
|
|
5
5
|
}
|
|
6
6
|
type StyleInput = string | StyleObject | CSSStyleDeclaration | Iterable<[string, StylePrimitive]>;
|
|
7
|
-
type
|
|
8
|
-
type SortMode = 'input' | 'tailwind' | 'grouped';
|
|
9
|
-
type CompressionMode = 'none' | 'safe' | 'aggressive';
|
|
10
|
-
type ColorMatchMode = 'exact' | 'nearest' | 'none';
|
|
11
|
-
type NumericMultiplierMode = 'all' | 'integer' | 'never';
|
|
12
|
-
type TwirlwindTheme = {
|
|
7
|
+
type Theme = {
|
|
13
8
|
spacing?: Record<string, string>;
|
|
14
9
|
colors?: Record<string, string>;
|
|
15
10
|
};
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
tailwindVersion?: '4';
|
|
19
|
-
theme?: TwirlwindTheme;
|
|
11
|
+
type Options = {
|
|
12
|
+
theme?: Theme;
|
|
20
13
|
allowArbitraryValues?: boolean;
|
|
21
14
|
allowArbitraryProperties?: boolean;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
sort?: SortMode;
|
|
15
|
+
compression?: 'none' | 'safe' | 'aggressive';
|
|
16
|
+
sort?: 'input' | 'tailwind' | 'grouped';
|
|
25
17
|
important?: boolean;
|
|
26
|
-
colorMatch?:
|
|
27
|
-
numericMultipliers?:
|
|
18
|
+
colorMatch?: 'exact' | 'nearest' | 'none';
|
|
19
|
+
numericMultipliers?: 'all' | 'integer' | 'never';
|
|
28
20
|
};
|
|
29
|
-
type
|
|
21
|
+
type Declaration = {
|
|
30
22
|
property: string;
|
|
31
23
|
value: string;
|
|
32
24
|
important: boolean;
|
|
33
25
|
variants: string[];
|
|
34
26
|
};
|
|
35
|
-
type ConvertedDeclaration =
|
|
27
|
+
type ConvertedDeclaration = Declaration & {
|
|
36
28
|
className: string;
|
|
37
29
|
kind: 'exact' | 'arbitrary';
|
|
38
30
|
};
|
|
39
|
-
type
|
|
40
|
-
declaration: CssDeclaration;
|
|
41
|
-
message: string;
|
|
42
|
-
};
|
|
43
|
-
type StyleToTailwindResult = {
|
|
31
|
+
type Result = {
|
|
44
32
|
className: string;
|
|
45
33
|
classes: string[];
|
|
46
34
|
exact: ConvertedDeclaration[];
|
|
47
35
|
arbitrary: ConvertedDeclaration[];
|
|
48
|
-
unmatched:
|
|
49
|
-
warnings: ConversionWarning[];
|
|
36
|
+
unmatched: Declaration[];
|
|
50
37
|
};
|
|
51
38
|
//#endregion
|
|
52
39
|
//#region src/index.d.ts
|
|
53
|
-
declare function
|
|
54
|
-
declare
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
declare function twirl(input: StyleInput, options?: Options): string;
|
|
41
|
+
declare namespace twirl {
|
|
42
|
+
var convert: (input: StyleInput, options?: Options) => Result;
|
|
43
|
+
}
|
|
57
44
|
//#endregion
|
|
58
|
-
export { type ConvertedDeclaration, type
|
|
45
|
+
export { type ConvertedDeclaration, type Declaration, type Options, type Result, type StyleInput, type StyleObject, type StylePrimitive, type Theme, twirl };
|
package/dist/index.mjs
CHANGED
|
@@ -3482,8 +3482,6 @@ const defaultTheme = {
|
|
|
3482
3482
|
};
|
|
3483
3483
|
function resolveOptions(options = {}) {
|
|
3484
3484
|
return {
|
|
3485
|
-
mode: options.mode ?? "pretty",
|
|
3486
|
-
tailwindVersion: "4",
|
|
3487
3485
|
theme: {
|
|
3488
3486
|
spacing: {
|
|
3489
3487
|
...defaultTheme.spacing,
|
|
@@ -3496,7 +3494,6 @@ function resolveOptions(options = {}) {
|
|
|
3496
3494
|
},
|
|
3497
3495
|
allowArbitraryValues: options.allowArbitraryValues ?? true,
|
|
3498
3496
|
allowArbitraryProperties: options.allowArbitraryProperties ?? true,
|
|
3499
|
-
preferThemeTokens: options.preferThemeTokens ?? true,
|
|
3500
3497
|
compression: options.compression ?? "safe",
|
|
3501
3498
|
sort: options.sort ?? "grouped",
|
|
3502
3499
|
important: options.important ?? false,
|
|
@@ -3506,40 +3503,27 @@ function resolveOptions(options = {}) {
|
|
|
3506
3503
|
}
|
|
3507
3504
|
//#endregion
|
|
3508
3505
|
//#region src/index.ts
|
|
3509
|
-
function
|
|
3510
|
-
const
|
|
3511
|
-
const
|
|
3506
|
+
function convert(input, options) {
|
|
3507
|
+
const resolved = resolveOptions(options);
|
|
3508
|
+
const pairs = expandShorthands(normalizeInput(input)).map((declaration) => ({
|
|
3512
3509
|
declaration,
|
|
3513
|
-
converted: convertDeclaration(declaration,
|
|
3510
|
+
converted: convertDeclaration(declaration, resolved)
|
|
3514
3511
|
}));
|
|
3515
|
-
const
|
|
3512
|
+
const sorted = sortConverted(compressConverted(pairs.flatMap(({ converted }) => {
|
|
3516
3513
|
if (!converted) return [];
|
|
3517
3514
|
return Array.isArray(converted) ? converted : [converted];
|
|
3518
|
-
}),
|
|
3519
|
-
const exact = convertedDeclarations.filter((converted) => converted.kind === "exact");
|
|
3520
|
-
const arbitrary = convertedDeclarations.filter((converted) => converted.kind === "arbitrary");
|
|
3521
|
-
const unmatched = conversionPairs.flatMap(({ declaration, converted }) => converted ? [] : [declaration]);
|
|
3522
|
-
const classes = convertedDeclarations.map(({ className }) => className);
|
|
3515
|
+
}), resolved), resolved);
|
|
3523
3516
|
return {
|
|
3524
|
-
className:
|
|
3525
|
-
classes,
|
|
3526
|
-
exact,
|
|
3527
|
-
arbitrary,
|
|
3528
|
-
unmatched,
|
|
3529
|
-
warnings: unmatched.map((declaration) => ({
|
|
3530
|
-
declaration,
|
|
3531
|
-
message: "No Tailwind utility or fallback could be emitted."
|
|
3532
|
-
}))
|
|
3517
|
+
className: sorted.map(({ className }) => className).join(" "),
|
|
3518
|
+
classes: sorted.map(({ className }) => className),
|
|
3519
|
+
exact: sorted.filter((c) => c.kind === "exact"),
|
|
3520
|
+
arbitrary: sorted.filter((c) => c.kind === "arbitrary"),
|
|
3521
|
+
unmatched: pairs.flatMap(({ declaration, converted }) => converted ? [] : [declaration])
|
|
3533
3522
|
};
|
|
3534
3523
|
}
|
|
3535
|
-
function
|
|
3536
|
-
return
|
|
3537
|
-
}
|
|
3538
|
-
function styleToClasses(input, options) {
|
|
3539
|
-
return styleToTailwind(input, options).classes;
|
|
3540
|
-
}
|
|
3541
|
-
function cssTextToTailwind(cssText, options) {
|
|
3542
|
-
return styleToTailwind(cssText, options);
|
|
3524
|
+
function twirl(input, options) {
|
|
3525
|
+
return convert(input, options).className;
|
|
3543
3526
|
}
|
|
3527
|
+
twirl.convert = convert;
|
|
3544
3528
|
//#endregion
|
|
3545
|
-
export {
|
|
3529
|
+
export { twirl };
|