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 CHANGED
@@ -10,100 +10,58 @@ Converts style objects, CSS declaration strings, and `CSSStyleDeclaration` value
10
10
  npm install twirlwind
11
11
  ```
12
12
 
13
- ## Quick start
13
+ ## Usage
14
14
 
15
15
  ```ts
16
- import { styleToClassName } from 'twirlwind'
16
+ import { twirl } from 'twirlwind'
17
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"
18
+ twirl({ display: 'flex', padding: '16px 8px', color: '#ef4444' })
19
+ // → "flex py-4 px-2 text-red-500"
25
20
  ```
26
21
 
27
- ## API
28
-
29
- ### `styleToTailwind(input, options?)`
22
+ ### Inputs
30
23
 
31
- Full conversion result with metadata.
24
+ `twirl()` accepts any of these and returns a class string:
32
25
 
33
26
  ```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?)`
27
+ // Style object
28
+ twirl({ backgroundColor: 'white', fontSize: '16px' })
47
29
 
48
- Returns just the class string.
30
+ // CSS string
31
+ twirl('display: flex; padding: 16px')
49
32
 
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.
33
+ // CSSStyleDeclaration (browser)
34
+ twirl(element.style)
58
35
 
59
- ```ts
60
- styleToClasses({ margin: '8px 16px' })
61
- // → ["my-2", "mx-4"]
36
+ // Computed styles
37
+ twirl(getComputedStyle(element))
62
38
  ```
63
39
 
64
- ### `cssTextToTailwind(cssText, options?)`
40
+ ### Detailed result
65
41
 
66
- Parses a CSS declaration string.
42
+ Use `twirl.convert()` when you need metadata:
67
43
 
68
44
  ```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')
45
+ const result = twirl.convert({ display: 'flex', width: '37px' })
81
46
 
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
- )
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 colors across formats to Tailwind's palette — OKLCH (v4), hex (v3), `rgb()`, keywords, and opacity modifiers.
58
+ Matches across formats — OKLCH, hex, `rgb()`, keywords, opacity modifiers.
99
59
 
100
60
  ```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"
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
- 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"
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 transforms and filters
77
+ ### Multi-value parsing
128
78
 
129
- Compound `transform` and `filter` declarations decompose into individual utility classes.
79
+ Compound `transform` and `filter` declarations decompose into individual classes.
130
80
 
131
81
  ```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"
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 when values match.
89
+ Expanded longhands compress to shorthand utilities.
142
90
 
143
91
  ```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"
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 — pseudo-classes, media queries, and container queries.
101
+ Nested objects map to Tailwind variants.
154
102
 
155
103
  ```ts
156
- styleToClassName({
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. Unknown properties use `[property:value]` syntax.
116
+ Every CSS property produces valid output.
176
117
 
177
118
  ```ts
178
- styleToClassName({ scrollTimelineName: '--main' })
179
- // "[scroll-timeline-name:--main]"
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
- styleToTailwind(input, {
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 shorthands** — `margin`, `border`, `font`, `background`, `transition`, `overflow`, `gap`, etc.
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 matching longhands back to axis/all shorthand utilities
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 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 = {
7
+ type Theme = {
13
8
  spacing?: Record<string, string>;
14
9
  colors?: Record<string, string>;
15
10
  };
16
- type StyleToTailwindOptions = {
17
- mode?: ConversionMode;
18
- tailwindVersion?: '4';
19
- theme?: TwirlwindTheme;
11
+ type Options = {
12
+ theme?: Theme;
20
13
  allowArbitraryValues?: boolean;
21
14
  allowArbitraryProperties?: boolean;
22
- preferThemeTokens?: boolean;
23
- compression?: CompressionMode;
24
- sort?: SortMode;
15
+ compression?: 'none' | 'safe' | 'aggressive';
16
+ sort?: 'input' | 'tailwind' | 'grouped';
25
17
  important?: boolean;
26
- colorMatch?: ColorMatchMode;
27
- numericMultipliers?: NumericMultiplierMode;
18
+ colorMatch?: 'exact' | 'nearest' | 'none';
19
+ numericMultipliers?: 'all' | 'integer' | 'never';
28
20
  };
29
- type CssDeclaration = {
21
+ type Declaration = {
30
22
  property: string;
31
23
  value: string;
32
24
  important: boolean;
33
25
  variants: string[];
34
26
  };
35
- type ConvertedDeclaration = CssDeclaration & {
27
+ type ConvertedDeclaration = Declaration & {
36
28
  className: string;
37
29
  kind: 'exact' | 'arbitrary';
38
30
  };
39
- type ConversionWarning = {
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: CssDeclaration[];
49
- warnings: ConversionWarning[];
36
+ unmatched: Declaration[];
50
37
  };
51
38
  //#endregion
52
39
  //#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;
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 CssDeclaration, type StyleInput, type StyleObject, type StylePrimitive, type StyleToTailwindOptions, type StyleToTailwindResult, type TwirlwindTheme, cssTextToTailwind, styleToClassName, styleToClasses, styleToTailwind };
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 styleToTailwind(input, options) {
3510
- const resolvedOptions = resolveOptions(options);
3511
- const conversionPairs = expandShorthands(normalizeInput(input)).map((declaration) => ({
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, resolvedOptions)
3510
+ converted: convertDeclaration(declaration, resolved)
3514
3511
  }));
3515
- const convertedDeclarations = sortConverted(compressConverted(conversionPairs.flatMap(({ converted }) => {
3512
+ const sorted = sortConverted(compressConverted(pairs.flatMap(({ converted }) => {
3516
3513
  if (!converted) return [];
3517
3514
  return Array.isArray(converted) ? converted : [converted];
3518
- }), resolvedOptions), resolvedOptions);
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: classes.join(" "),
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 styleToClassName(input, options) {
3536
- return styleToTailwind(input, options).className;
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 { cssTextToTailwind, styleToClassName, styleToClasses, styleToTailwind };
3529
+ export { twirl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twirlwind",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Tailwind v4-first CSS style object to utility class serializer with lossless arbitrary-property fallback.",
5
5
  "keywords": [
6
6
  "converter",