twirlwind 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Danila Poyarkov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Twirlwind
2
+
3
+ Tailwind v4-first CSS-to-utility-class serializer for JavaScript/TypeScript.
4
+
5
+ Converts style objects, CSS declaration strings, and `CSSStyleDeclaration` values into clean Tailwind utility classes. Prefers canonical utilities, falls back to arbitrary values, then arbitrary properties — every CSS property produces valid output.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install twirlwind
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { styleToClassName } from 'twirlwind'
17
+
18
+ styleToClassName({
19
+ display: 'flex',
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"
25
+ ```
26
+
27
+ ## API
28
+
29
+ ### `styleToTailwind(input, options?)`
30
+
31
+ Full conversion result with metadata.
32
+
33
+ ```ts
34
+ import { styleToTailwind } from 'twirlwind'
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?)`
47
+
48
+ Returns just the class string.
49
+
50
+ ```ts
51
+ styleToClassName({ padding: '16px', color: 'white' })
52
+ // → "p-4 text-white"
53
+ ```
54
+
55
+ ### `styleToClasses(input, options?)`
56
+
57
+ Returns an array of class strings.
58
+
59
+ ```ts
60
+ styleToClasses({ margin: '8px 16px' })
61
+ // → ["my-2", "mx-4"]
62
+ ```
63
+
64
+ ### `cssTextToTailwind(cssText, options?)`
65
+
66
+ Parses a CSS declaration string.
67
+
68
+ ```ts
69
+ cssTextToTailwind('display: flex; gap: 16px; padding: 8px')
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')
81
+
82
+ // CSSStyleDeclaration (browser)
83
+ styleToClassName(element.style)
84
+
85
+ // Iterable of [property, value] pairs
86
+ styleToClassName(
87
+ new Map([
88
+ ['display', 'flex'],
89
+ ['padding', '16px']
90
+ ])
91
+ )
92
+ ```
93
+
94
+ ## Features
95
+
96
+ ### Color matching
97
+
98
+ Matches colors across formats to Tailwind's palette — OKLCH (v4), hex (v3), `rgb()`, keywords, and opacity modifiers.
99
+
100
+ ```ts
101
+ styleToClassName({ color: '#ef4444' }) // "text-red-500"
102
+ styleToClassName({ color: 'rgb(59 130 246)' }) // "text-blue-500"
103
+ styleToClassName({ color: 'oklch(62.3% 0.214 259.815)' }) // "text-blue-500"
104
+ styleToClassName({ color: 'oklch(62.3% 0.214 259.815 / 50%)' }) // "text-blue-500/50"
105
+ styleToClassName({ color: 'inherit' }) // "text-inherit"
106
+ styleToClassName({ color: 'currentColor' }) // "text-current"
107
+ ```
108
+
109
+ ### Shorthand expansion
110
+
111
+ CSS shorthands decompose into Tailwind longhands.
112
+
113
+ ```ts
114
+ styleToClassName({ border: '2px solid #ef4444' })
115
+ // → "border-2 border-red-500 border-solid"
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"
125
+ ```
126
+
127
+ ### Multi-value transforms and filters
128
+
129
+ Compound `transform` and `filter` declarations decompose into individual utility classes.
130
+
131
+ ```ts
132
+ styleToClassName({ transform: 'translateX(8px) rotate(45deg)' })
133
+ // → "rotate-45 translate-x-2"
134
+
135
+ styleToClassName({ filter: 'blur(8px) brightness(0.75)' })
136
+ // → "blur brightness-75"
137
+ ```
138
+
139
+ ### Compression
140
+
141
+ Expanded longhands compress to shorthand utilities when values match.
142
+
143
+ ```ts
144
+ styleToClassName({ margin: '8px' }) // "m-2" (not "mt-2 mr-2 mb-2 ml-2")
145
+ styleToClassName({ inset: '0' }) // "inset-0"
146
+ styleToClassName({ padding: '8px 16px' }) // "py-2 px-4"
147
+ styleToClassName({ borderRadius: '8px' }) // "rounded-lg"
148
+ styleToClassName({ gap: '12px 12px' }) // "gap-3"
149
+ ```
150
+
151
+ ### Variants
152
+
153
+ Nested objects map to Tailwind variants — pseudo-classes, media queries, and container queries.
154
+
155
+ ```ts
156
+ styleToClassName({
157
+ color: 'white',
158
+ ':hover': { color: '#3b82f6' },
159
+ '@media (min-width: 768px)': { display: 'grid' },
160
+ '@media (prefers-color-scheme: dark)': { backgroundColor: 'black' },
161
+ '@container (min-width: 512px)': { display: 'flex' }
162
+ })
163
+ // → "text-white hover:text-blue-500 md:grid dark:bg-black @lg:flex"
164
+ ```
165
+
166
+ ### Scroll snap decomposition
167
+
168
+ ```ts
169
+ styleToClassName({ scrollSnapType: 'x mandatory' })
170
+ // → "snap-x snap-mandatory"
171
+ ```
172
+
173
+ ### Arbitrary fallback
174
+
175
+ Every CSS property produces valid output. Unknown properties use `[property:value]` syntax.
176
+
177
+ ```ts
178
+ styleToClassName({ scrollTimelineName: '--main' })
179
+ // → "[scroll-timeline-name:--main]"
180
+
181
+ styleToClassName({ width: '37px' })
182
+ // → "w-[37px]"
183
+ ```
184
+
185
+ ## Options
186
+
187
+ ```ts
188
+ styleToTailwind(input, {
189
+ // Allow arbitrary value utilities like w-[37px]
190
+ allowArbitraryValues: true, // default: true
191
+
192
+ // Allow arbitrary property fallbacks like [scroll-timeline-name:--x]
193
+ allowArbitraryProperties: true, // default: true
194
+
195
+ // Compress matching longhands to shorthands
196
+ compression: 'safe', // "none" | "safe" | "aggressive"
197
+
198
+ // Sort output classes
199
+ sort: 'grouped', // "input" | "tailwind" | "grouped"
200
+
201
+ // Color matching strategy
202
+ colorMatch: 'exact', // "exact" | "nearest" | "none"
203
+
204
+ // Spacing token multiplier mode
205
+ numericMultipliers: 'integer', // "all" | "integer" | "never"
206
+
207
+ // Custom theme colors/spacing
208
+ theme: {
209
+ colors: { brand: '#ff6600' },
210
+ spacing: { '18': '4.5rem' }
211
+ }
212
+ })
213
+ ```
214
+
215
+ ## How it works
216
+
217
+ 1. **Normalize** — camelCase → kebab-case, numeric → px, vendor prefixes, `!important`
218
+ 2. **Expand shorthands** — `margin`, `border`, `font`, `background`, `transition`, `overflow`, `gap`, etc.
219
+ 3. **Convert** — exact utility → value alias → spacing token → color match → arbitrary value → arbitrary property
220
+ 4. **Compress** — merge matching longhands back to axis/all shorthand utilities
221
+ 5. **Sort** — deterministic output ordering
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1,58 @@
1
+ //#region src/types.d.ts
2
+ type StylePrimitive = string | number | null | undefined;
3
+ interface StyleObject {
4
+ [property: string]: StylePrimitive | StyleObject;
5
+ }
6
+ type StyleInput = string | StyleObject | CSSStyleDeclaration | Iterable<[string, StylePrimitive]>;
7
+ type ConversionMode = 'pretty' | 'lossless' | 'strict';
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 = {
13
+ spacing?: Record<string, string>;
14
+ colors?: Record<string, string>;
15
+ };
16
+ type StyleToTailwindOptions = {
17
+ mode?: ConversionMode;
18
+ tailwindVersion?: '4';
19
+ theme?: TwirlwindTheme;
20
+ allowArbitraryValues?: boolean;
21
+ allowArbitraryProperties?: boolean;
22
+ preferThemeTokens?: boolean;
23
+ compression?: CompressionMode;
24
+ sort?: SortMode;
25
+ important?: boolean;
26
+ colorMatch?: ColorMatchMode;
27
+ numericMultipliers?: NumericMultiplierMode;
28
+ };
29
+ type CssDeclaration = {
30
+ property: string;
31
+ value: string;
32
+ important: boolean;
33
+ variants: string[];
34
+ };
35
+ type ConvertedDeclaration = CssDeclaration & {
36
+ className: string;
37
+ kind: 'exact' | 'arbitrary';
38
+ };
39
+ type ConversionWarning = {
40
+ declaration: CssDeclaration;
41
+ message: string;
42
+ };
43
+ type StyleToTailwindResult = {
44
+ className: string;
45
+ classes: string[];
46
+ exact: ConvertedDeclaration[];
47
+ arbitrary: ConvertedDeclaration[];
48
+ unmatched: CssDeclaration[];
49
+ warnings: ConversionWarning[];
50
+ };
51
+ //#endregion
52
+ //#region src/index.d.ts
53
+ declare function styleToTailwind(input: StyleInput, options?: StyleToTailwindOptions): StyleToTailwindResult;
54
+ declare function styleToClassName(input: StyleInput, options?: StyleToTailwindOptions): string;
55
+ declare function styleToClasses(input: StyleInput, options?: StyleToTailwindOptions): string[];
56
+ declare function cssTextToTailwind(cssText: string, options?: StyleToTailwindOptions): StyleToTailwindResult;
57
+ //#endregion
58
+ export { type ConvertedDeclaration, type CssDeclaration, type StyleInput, type StyleObject, type StylePrimitive, type StyleToTailwindOptions, type StyleToTailwindResult, type TwirlwindTheme, cssTextToTailwind, styleToClassName, styleToClasses, styleToTailwind };