postcss-ruler 1.0.1 → 1.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 +237 -30
- package/index.js +398 -258
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,12 +9,14 @@ A PostCSS plugin that generates fluid CSS scales and values using the `clamp()`
|
|
|
9
9
|
Working with design teams often means dealing with sizes that don't follow perfect mathematical ratios. A designer might spec `16px → 24px` for one size and `32px → 56px` for another based on visual harmony rather than a type scale.
|
|
10
10
|
|
|
11
11
|
postcss-ruler embraces this reality. You get:
|
|
12
|
+
|
|
12
13
|
- **Fine-tuned control**: Set exact min and max values per size
|
|
13
14
|
- **Design tool compatibility**: Match your Figma specs precisely
|
|
14
15
|
- **Fluid behavior**: Smooth scaling between breakpoints via `clamp()`
|
|
15
16
|
- **Cross pairs**: Generate spacing between any two sizes (explained below)
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
19
|
+
|
|
18
20
|
```bash
|
|
19
21
|
npm install postcss-ruler
|
|
20
22
|
```
|
|
@@ -22,24 +24,26 @@ npm install postcss-ruler
|
|
|
22
24
|
## Usage
|
|
23
25
|
|
|
24
26
|
Add the plugin to your PostCSS configuration:
|
|
27
|
+
|
|
25
28
|
```javascript
|
|
26
29
|
// postcss.config.js
|
|
27
30
|
module.exports = {
|
|
28
31
|
plugins: {
|
|
29
|
-
|
|
30
|
-
minWidth: 320,
|
|
31
|
-
maxWidth: 1760,
|
|
32
|
-
generateAllCrossPairs: false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
32
|
+
"postcss-ruler": {
|
|
33
|
+
minWidth: 320, // Default minimum viewport width
|
|
34
|
+
maxWidth: 1760, // Default maximum viewport width
|
|
35
|
+
generateAllCrossPairs: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
36
39
|
```
|
|
37
40
|
|
|
38
41
|
## Features
|
|
39
42
|
|
|
40
|
-
### 1.
|
|
43
|
+
### 1. Scale Generation: Create Fluid Scales
|
|
41
44
|
|
|
42
45
|
Create multiple CSS custom properties from named size pairs:
|
|
46
|
+
|
|
43
47
|
```css
|
|
44
48
|
@ruler scale({
|
|
45
49
|
minWidth: 320,
|
|
@@ -57,6 +61,7 @@ Create multiple CSS custom properties from named size pairs:
|
|
|
57
61
|
```
|
|
58
62
|
|
|
59
63
|
**Generates:**
|
|
64
|
+
|
|
60
65
|
```css
|
|
61
66
|
--space-xs: clamp(0.5rem, 0.4545vw + 0.3636rem, 1rem);
|
|
62
67
|
--space-sm: clamp(1rem, 0.4545vw + 0.8636rem, 1.5rem);
|
|
@@ -74,6 +79,7 @@ For example, if you have `xs: [8, 16]` and `lg: [32, 48]`, a cross pair `xs-lg`
|
|
|
74
79
|
**Without cross pairs**, you only get the sizes you explicitly define.
|
|
75
80
|
|
|
76
81
|
**With `generateAllCrossPairs: true`**, you get every possible combination:
|
|
82
|
+
|
|
77
83
|
```css
|
|
78
84
|
@ruler scale({
|
|
79
85
|
prefix: 'space',
|
|
@@ -87,6 +93,7 @@ For example, if you have `xs: [8, 16]` and `lg: [32, 48]`, a cross pair `xs-lg`
|
|
|
87
93
|
```
|
|
88
94
|
|
|
89
95
|
**Generates:**
|
|
96
|
+
|
|
90
97
|
```css
|
|
91
98
|
/* Your defined pairs */
|
|
92
99
|
--space-xs: clamp(0.5rem, ...);
|
|
@@ -94,21 +101,111 @@ For example, if you have `xs: [8, 16]` and `lg: [32, 48]`, a cross pair `xs-lg`
|
|
|
94
101
|
--space-lg: clamp(2rem, ...);
|
|
95
102
|
|
|
96
103
|
/* All cross combinations */
|
|
97
|
-
--space-xs-md: clamp(0.5rem, 1.3636vw + 0.3636rem, 2rem);
|
|
98
|
-
--space-xs-lg: clamp(0.5rem, 2.2727vw + 0.3636rem, 3rem);
|
|
99
|
-
--space-md-lg: clamp(1.5rem, 0.9091vw + 1.3636rem, 3rem);
|
|
104
|
+
--space-xs-md: clamp(0.5rem, 1.3636vw + 0.3636rem, 2rem); /* 8px → 32px */
|
|
105
|
+
--space-xs-lg: clamp(0.5rem, 2.2727vw + 0.3636rem, 3rem); /* 8px → 48px */
|
|
106
|
+
--space-md-lg: clamp(1.5rem, 0.9091vw + 1.3636rem, 3rem); /* 24px → 48px */
|
|
100
107
|
```
|
|
101
108
|
|
|
102
109
|
**Use case**: Section padding that needs more dramatic scaling than your base sizes allow.
|
|
110
|
+
|
|
103
111
|
```css
|
|
104
112
|
.hero {
|
|
105
113
|
padding-block: var(--space-md-xl); /* Scales across a wider range */
|
|
106
114
|
}
|
|
107
115
|
```
|
|
108
116
|
|
|
109
|
-
### 2.
|
|
117
|
+
### 2. Utility Class Generation: Auto-Generate Utility Classes
|
|
118
|
+
|
|
119
|
+
Generate utility classes from your defined scales with complete selector flexibility:
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
@ruler scale({
|
|
123
|
+
prefix: 'space',
|
|
124
|
+
pairs: {
|
|
125
|
+
"xs": [8, 16],
|
|
126
|
+
"sm": [16, 24],
|
|
127
|
+
"md": [24, 32]
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/* Basic class selector */
|
|
132
|
+
@ruler utility({
|
|
133
|
+
selector: '.gap',
|
|
134
|
+
property: 'gap',
|
|
135
|
+
scale: 'space'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/* Nested selector with & (for PostCSS nesting) */
|
|
139
|
+
@ruler utility({
|
|
140
|
+
selector: '&.active',
|
|
141
|
+
property: 'padding',
|
|
142
|
+
scale: 'space'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/* Multiple properties */
|
|
146
|
+
@ruler utility({
|
|
147
|
+
selector: '.p-block',
|
|
148
|
+
property: ['padding-top', 'padding-bottom'],
|
|
149
|
+
scale: 'space'
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Generates:**
|
|
154
|
+
|
|
155
|
+
```css
|
|
156
|
+
--space-xs: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
157
|
+
--space-sm: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
158
|
+
--space-md: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
159
|
+
|
|
160
|
+
.gap-xs {
|
|
161
|
+
gap: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
162
|
+
}
|
|
163
|
+
.gap-sm {
|
|
164
|
+
gap: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
165
|
+
}
|
|
166
|
+
.gap-md {
|
|
167
|
+
gap: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&.active-xs {
|
|
171
|
+
padding: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
172
|
+
}
|
|
173
|
+
&.active-sm {
|
|
174
|
+
padding: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
175
|
+
}
|
|
176
|
+
&.active-md {
|
|
177
|
+
padding: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.p-block-xs {
|
|
181
|
+
padding-top: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
182
|
+
padding-bottom: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
183
|
+
}
|
|
184
|
+
/* ... */
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Supported selector patterns:**
|
|
188
|
+
|
|
189
|
+
- **Class selectors**: `.gap` → `.gap-xs`, `.gap-sm`, `.gap-md`
|
|
190
|
+
- **Nested with &**: `&.active` → `&.active-xs`, `&.active-sm` (PostCSS nesting)
|
|
191
|
+
- **Multiple classes**: `.container.space` → `.container.space-xs`, etc.
|
|
192
|
+
- **ID selectors**: `#section` → `#section-xs`, `#section-sm`
|
|
193
|
+
- **Element selectors**: `section` → `section-xs`, `section-sm`
|
|
194
|
+
- **Parent context**: `.container &` → `.container &-xs`, etc.
|
|
195
|
+
|
|
196
|
+
**Utility options:**
|
|
197
|
+
|
|
198
|
+
| Option | Type | Required | Description |
|
|
199
|
+
| ----------------------- | --------------- | -------- | --------------------------------------------------------------------- |
|
|
200
|
+
| `selector` | string | Yes | Any valid CSS selector pattern (e.g., `.gap`, `&.active`, `#section`) |
|
|
201
|
+
| `property` | string or array | Yes | CSS property name(s) to apply the scale values to |
|
|
202
|
+
| `scale` | string | Yes | Name of a previously defined scale (the `prefix` value) |
|
|
203
|
+
| `generateAllCrossPairs` | boolean | No | Include/exclude cross-pairs (overrides scale default) |
|
|
204
|
+
|
|
205
|
+
### 3. Inline Mode: Fluid Function
|
|
110
206
|
|
|
111
207
|
Convert individual values directly to `clamp()` functions:
|
|
208
|
+
|
|
112
209
|
```css
|
|
113
210
|
.element {
|
|
114
211
|
/* Uses default minWidth/maxWidth from config */
|
|
@@ -123,11 +220,13 @@ Convert individual values directly to `clamp()` functions:
|
|
|
123
220
|
```
|
|
124
221
|
|
|
125
222
|
**Generates:**
|
|
223
|
+
|
|
126
224
|
```css
|
|
127
225
|
.element {
|
|
128
226
|
font-size: clamp(1rem, 0.4545vw + 0.8636rem, 1.5rem);
|
|
129
227
|
padding: clamp(0.75rem, 0.9091vw + 0.4773rem, 1.25rem);
|
|
130
|
-
margin: clamp(0.5rem, 0.4545vw + 0.3636rem, 1rem)
|
|
228
|
+
margin: clamp(0.5rem, 0.4545vw + 0.3636rem, 1rem)
|
|
229
|
+
clamp(1rem, 0.9091vw + 0.7273rem, 2rem);
|
|
131
230
|
}
|
|
132
231
|
```
|
|
133
232
|
|
|
@@ -135,23 +234,23 @@ Convert individual values directly to `clamp()` functions:
|
|
|
135
234
|
|
|
136
235
|
### Plugin Options
|
|
137
236
|
|
|
138
|
-
| Option
|
|
139
|
-
|
|
140
|
-
| `minWidth`
|
|
141
|
-
| `maxWidth`
|
|
237
|
+
| Option | Type | Default | Description |
|
|
238
|
+
| ----------------------- | ------- | ------- | ----------------------------------------- |
|
|
239
|
+
| `minWidth` | number | `320` | Default minimum viewport width in pixels |
|
|
240
|
+
| `maxWidth` | number | `1760` | Default maximum viewport width in pixels |
|
|
142
241
|
| `generateAllCrossPairs` | boolean | `false` | Generate cross-combinations in scale mode |
|
|
143
242
|
|
|
144
243
|
### At-Rule Options
|
|
145
244
|
|
|
146
245
|
All options can be overridden per `@ruler scale()` declaration:
|
|
147
246
|
|
|
148
|
-
| Option
|
|
149
|
-
|
|
150
|
-
| `minWidth`
|
|
151
|
-
| `maxWidth`
|
|
152
|
-
| `prefix`
|
|
153
|
-
| `generateAllCrossPairs` | boolean | `false`
|
|
154
|
-
| `pairs`
|
|
247
|
+
| Option | Type | Default | Description |
|
|
248
|
+
| ----------------------- | ------- | --------- | ------------------------------------------ |
|
|
249
|
+
| `minWidth` | number | `320` | Minimum viewport width for this scale |
|
|
250
|
+
| `maxWidth` | number | `1760` | Maximum viewport width for this scale |
|
|
251
|
+
| `prefix` | string | `"space"` | Prefix for generated CSS custom properties |
|
|
252
|
+
| `generateAllCrossPairs` | boolean | `false` | Generate cross-combinations for this scale |
|
|
253
|
+
| `pairs` | object | required | Size pairs as `"name": [min, max]` |
|
|
155
254
|
|
|
156
255
|
### Inline Function Syntax
|
|
157
256
|
|
|
@@ -159,12 +258,62 @@ All options can be overridden per `@ruler scale()` declaration:
|
|
|
159
258
|
ruler.fluid(minSize, maxSize[, minWidth, maxWidth])
|
|
160
259
|
```
|
|
161
260
|
|
|
162
|
-
| Parameter
|
|
163
|
-
|
|
164
|
-
| `minSize`
|
|
165
|
-
| `maxSize`
|
|
166
|
-
| `minWidth` | number | No
|
|
167
|
-
| `maxWidth` | number | No
|
|
261
|
+
| Parameter | Type | Required | Description |
|
|
262
|
+
| ---------- | ------ | -------- | -------------------------------------------- |
|
|
263
|
+
| `minSize` | number | Yes | Minimum size in pixels |
|
|
264
|
+
| `maxSize` | number | Yes | Maximum size in pixels |
|
|
265
|
+
| `minWidth` | number | No | Minimum viewport width (uses config default) |
|
|
266
|
+
| `maxWidth` | number | No | Maximum viewport width (uses config default) |
|
|
267
|
+
|
|
268
|
+
### Static Values
|
|
269
|
+
|
|
270
|
+
When min and max values are equal, postcss-ruler outputs a simple rem value instead of a clamp() function. This works in all three modes:
|
|
271
|
+
|
|
272
|
+
**Inline function:**
|
|
273
|
+
|
|
274
|
+
```css
|
|
275
|
+
.element {
|
|
276
|
+
font-size: ruler.fluid(20, 20);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Generates:**
|
|
281
|
+
|
|
282
|
+
```css
|
|
283
|
+
.element {
|
|
284
|
+
font-size: 1.25rem;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Scale generation:**
|
|
289
|
+
|
|
290
|
+
```css
|
|
291
|
+
@ruler scale({
|
|
292
|
+
prefix: 'size',
|
|
293
|
+
pairs: {
|
|
294
|
+
"static": [16, 16],
|
|
295
|
+
"fluid": [16, 24]
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Generates:**
|
|
301
|
+
|
|
302
|
+
```css
|
|
303
|
+
--size-static: 1rem;
|
|
304
|
+
--size-fluid: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Use case:** Mix static and fluid values in the same scale for consistency. Define your base unit as a static value and let other sizes scale fluidly from it.
|
|
308
|
+
|
|
309
|
+
### Utility Options
|
|
310
|
+
|
|
311
|
+
| Option | Type | Default | Description |
|
|
312
|
+
| ----------------------- | --------------- | -------- | --------------------------------------------------------------------- |
|
|
313
|
+
| `selector` | string | required | Any valid CSS selector pattern (e.g., `.gap`, `&.active`, `#section`) |
|
|
314
|
+
| `property` | string or array | required | CSS property name(s) to apply the scale values to |
|
|
315
|
+
| `scale` | string | required | Name of a previously defined scale (the `prefix` value) |
|
|
316
|
+
| `generateAllCrossPairs` | boolean | No | Include/exclude cross-pairs (overrides scale default) |
|
|
168
317
|
|
|
169
318
|
## How It Works
|
|
170
319
|
|
|
@@ -189,6 +338,63 @@ At 1760px viewport: `1.5rem` (24px)
|
|
|
189
338
|
|
|
190
339
|
## Use Cases
|
|
191
340
|
|
|
341
|
+
### Utility-First Workflow with Fluid Scales
|
|
342
|
+
|
|
343
|
+
Generate a complete set of utility classes from your design system:
|
|
344
|
+
|
|
345
|
+
```css
|
|
346
|
+
@ruler scale({
|
|
347
|
+
prefix: 'space',
|
|
348
|
+
generateAllCrossPairs: true,
|
|
349
|
+
pairs: {
|
|
350
|
+
"xs": [8, 16],
|
|
351
|
+
"sm": [12, 20],
|
|
352
|
+
"md": [16, 28],
|
|
353
|
+
"lg": [24, 40],
|
|
354
|
+
"xl": [32, 56]
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
/* Gap utilities */
|
|
359
|
+
@ruler utility({
|
|
360
|
+
selector: '.gap',
|
|
361
|
+
property: 'gap',
|
|
362
|
+
scale: 'space'
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
/* Padding utilities */
|
|
366
|
+
@ruler utility({
|
|
367
|
+
selector: '.p',
|
|
368
|
+
property: 'padding',
|
|
369
|
+
scale: 'space'
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
/* Margin utilities */
|
|
373
|
+
@ruler utility({
|
|
374
|
+
selector: '.m',
|
|
375
|
+
property: 'margin',
|
|
376
|
+
scale: 'space'
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
/* Stack spacing (for flow layout) */
|
|
380
|
+
@ruler utility({
|
|
381
|
+
selector: '.stack > * + *',
|
|
382
|
+
property: 'margin-top',
|
|
383
|
+
scale: 'space'
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Use in your HTML:
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<section class="p-lg gap-md">
|
|
391
|
+
<div class="stack gap-sm">
|
|
392
|
+
<h2>Heading</h2>
|
|
393
|
+
<p>Content that scales smoothly</p>
|
|
394
|
+
</div>
|
|
395
|
+
</section>
|
|
396
|
+
```
|
|
397
|
+
|
|
192
398
|
### Responsive Typography
|
|
193
399
|
|
|
194
400
|
```css
|
|
@@ -260,6 +466,7 @@ h2 {
|
|
|
260
466
|
## Browser Support
|
|
261
467
|
|
|
262
468
|
The `clamp()` function is supported in all modern browsers:
|
|
469
|
+
|
|
263
470
|
- Chrome 79+
|
|
264
471
|
- Firefox 75+
|
|
265
472
|
- Safari 13.1+
|
package/index.js
CHANGED
|
@@ -1,279 +1,419 @@
|
|
|
1
|
-
const CSSValueParser = require(
|
|
1
|
+
const CSSValueParser = require("postcss-value-parser");
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @type {import('postcss').PluginCreator}
|
|
5
5
|
*/
|
|
6
|
-
module.exports = opts => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
6
|
+
module.exports = (opts) => {
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
minWidth: 320,
|
|
9
|
+
maxWidth: 1760,
|
|
10
|
+
generateAllCrossPairs: false,
|
|
11
|
+
};
|
|
12
|
+
const config = Object.assign(DEFAULTS, opts);
|
|
13
|
+
|
|
14
|
+
// Storage for generated scales
|
|
15
|
+
const scales = {};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Converts pixels to r]em units
|
|
19
|
+
* @param {number} px - Pixel value to convert
|
|
20
|
+
* @returns {string} Rem value as string
|
|
21
|
+
*/
|
|
22
|
+
const pxToRem = (px) => `${parseFloat((px / 16).toFixed(4))}rem`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates that min value is less than max value
|
|
26
|
+
* @param {number} min - Minimum value
|
|
27
|
+
* @param {number} max - Maximum value
|
|
28
|
+
* @param {string} context - Context for error message
|
|
29
|
+
* @throws {Error} If min >= max
|
|
30
|
+
*/
|
|
31
|
+
const validateMinMax = (min, max, context) => {
|
|
32
|
+
if (min >= max) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`[postcss-ruler] Invalid ${context}: min (${min}) must be less than max (${max})`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculates a fluid clamp() function
|
|
41
|
+
* @param {Object} params - Calculation parameters
|
|
42
|
+
* @param {number} params.minSize - Minimum size in pixels
|
|
43
|
+
* @param {number} params.maxSize - Maximum size in pixels
|
|
44
|
+
* @param {number} params.minWidth - Minimum viewport width in pixels
|
|
45
|
+
* @param {number} params.maxWidth - Maximum viewport width in pixels
|
|
46
|
+
* @returns {string} CSS clamp() function
|
|
47
|
+
*/
|
|
48
|
+
const calculateClamp = ({ minSize, maxSize, minWidth, maxWidth }) => {
|
|
49
|
+
// New: Allow equal values - just return rem
|
|
50
|
+
if (minSize === maxSize) {
|
|
51
|
+
return pxToRem(minSize);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
validateMinMax(minSize, maxSize, "size");
|
|
55
|
+
validateMinMax(minWidth, maxWidth, "width");
|
|
56
|
+
|
|
57
|
+
const slope = (maxSize - minSize) / (maxWidth - minWidth);
|
|
58
|
+
const intersect = -minWidth * slope + minSize;
|
|
59
|
+
|
|
60
|
+
return `clamp(${pxToRem(minSize)}, ${(slope * 100).toFixed(
|
|
61
|
+
4,
|
|
62
|
+
)}vw + ${pxToRem(intersect)}, ${pxToRem(maxSize)})`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates clamp values from pairs
|
|
67
|
+
* @param {Object} params - Generation parameters
|
|
68
|
+
* @param {Array<{name: string, values: [number, number]}>} params.pairs - Size pairs
|
|
69
|
+
* @param {number} params.minWidth - Minimum viewport width
|
|
70
|
+
* @param {number} params.maxWidth - Maximum viewport width
|
|
71
|
+
* @param {boolean} params.generateAllCrossPairs - Whether to generate cross pairs
|
|
72
|
+
* @returns {Array<{label: string, clamp: string}>} Array of clamp values
|
|
73
|
+
*/
|
|
74
|
+
const generateClamps = ({
|
|
75
|
+
pairs,
|
|
76
|
+
minWidth,
|
|
77
|
+
maxWidth,
|
|
78
|
+
generateAllCrossPairs,
|
|
79
|
+
}) => {
|
|
80
|
+
let clampScales = pairs.map(({ name, values: [minSize, maxSize] }) => ({
|
|
81
|
+
label: name,
|
|
82
|
+
clamp: calculateClamp({
|
|
83
|
+
minSize,
|
|
84
|
+
maxSize,
|
|
68
85
|
minWidth,
|
|
69
86
|
maxWidth,
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
}),
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
if (generateAllCrossPairs) {
|
|
91
|
+
let crossPairs = [];
|
|
92
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
93
|
+
for (let j = i + 1; j < pairs.length; j++) {
|
|
94
|
+
const [smaller, larger] = [pairs[i], pairs[j]].sort(
|
|
95
|
+
(a, b) => a.values[0] - b.values[0],
|
|
96
|
+
);
|
|
97
|
+
crossPairs.push({
|
|
98
|
+
label: `${smaller.name}-${larger.name}`,
|
|
74
99
|
clamp: calculateClamp({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
minSize: smaller.values[0],
|
|
101
|
+
maxSize: larger.values[1],
|
|
102
|
+
minWidth,
|
|
103
|
+
maxWidth,
|
|
79
104
|
}),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (generateAllCrossPairs) {
|
|
83
|
-
let crossPairs = [];
|
|
84
|
-
for (let i = 0; i < pairs.length; i++) {
|
|
85
|
-
for (let j = i + 1; j < pairs.length; j++) {
|
|
86
|
-
const [smaller, larger] = [pairs[i], pairs[j]].sort(
|
|
87
|
-
(a, b) => a.values[0] - b.values[0]
|
|
88
|
-
);
|
|
89
|
-
crossPairs.push({
|
|
90
|
-
label: `${smaller.name}-${larger.name}`,
|
|
91
|
-
clamp: calculateClamp({
|
|
92
|
-
minSize: smaller.values[0],
|
|
93
|
-
maxSize: larger.values[1],
|
|
94
|
-
minWidth,
|
|
95
|
-
maxWidth,
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
clampScales = [...clampScales, ...crossPairs];
|
|
105
|
+
});
|
|
101
106
|
}
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
}
|
|
108
|
+
clampScales = [...clampScales, ...crossPairs];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return clampScales;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parses parameters from @fluid at-rule
|
|
116
|
+
* @param {Array} params - Parsed parameter nodes
|
|
117
|
+
* @returns {Object} Parsed configuration object
|
|
118
|
+
*/
|
|
119
|
+
const parseAtRuleParams = (params) => {
|
|
120
|
+
const clampsParams = {
|
|
121
|
+
minWidth: config.minWidth,
|
|
122
|
+
maxWidth: config.maxWidth,
|
|
123
|
+
pairs: {},
|
|
124
|
+
prefix: "space",
|
|
125
|
+
generateAllCrossPairs: config.generateAllCrossPairs,
|
|
104
126
|
};
|
|
105
127
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
for (let i = 0; i < params.length; i++) {
|
|
129
|
+
const param = params[i];
|
|
130
|
+
const nextParam = params[i + 1];
|
|
131
|
+
if (!param || !nextParam) continue;
|
|
132
|
+
const key = param.value;
|
|
133
|
+
let value = nextParam.value.replace(/[:,]/g, "");
|
|
134
|
+
|
|
135
|
+
switch (key) {
|
|
136
|
+
case "minWidth":
|
|
137
|
+
case "maxWidth":
|
|
138
|
+
clampsParams[key] = Number(value);
|
|
139
|
+
i++;
|
|
140
|
+
break;
|
|
141
|
+
case "prefix":
|
|
142
|
+
clampsParams.prefix = value.replace(/['"]/g, "");
|
|
143
|
+
i++;
|
|
144
|
+
break;
|
|
145
|
+
case "generateAllCrossPairs":
|
|
146
|
+
clampsParams.generateAllCrossPairs = value === "true";
|
|
147
|
+
i++;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return clampsParams;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extracts pairs from parsed parameters
|
|
157
|
+
* @param {Array} params - Parsed parameter nodes
|
|
158
|
+
* @returns {Object} Pairs object with name: [min, max] entries
|
|
159
|
+
*/
|
|
160
|
+
const extractPairs = (params) => {
|
|
161
|
+
const pairs = {};
|
|
162
|
+
const pairsStartIndex = params.findIndex((x) => x.value === "pairs");
|
|
163
|
+
|
|
164
|
+
if (pairsStartIndex === -1) return pairs;
|
|
165
|
+
|
|
166
|
+
let currentName = null;
|
|
167
|
+
let currentValues = [];
|
|
168
|
+
|
|
169
|
+
for (let i = pairsStartIndex + 1; i < params.length; i++) {
|
|
170
|
+
const param = params[i];
|
|
171
|
+
const value = param.value.replace("[", "").replace("]", "");
|
|
172
|
+
if (!value || value === "[" || value === "]") continue;
|
|
173
|
+
|
|
174
|
+
if (param.type === "string") {
|
|
175
|
+
if (currentName && currentValues.length === 2) {
|
|
176
|
+
pairs[currentName] = currentValues;
|
|
147
177
|
}
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
currentName = value;
|
|
179
|
+
currentValues = [];
|
|
180
|
+
} else {
|
|
181
|
+
const numValue = Number(value);
|
|
182
|
+
if (!isNaN(numValue)) currentValues.push(numValue);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (currentName && currentValues.length === 2) {
|
|
186
|
+
pairs[currentName] = currentValues;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return pairs;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Parses parameters from @ruler utility() at-rule
|
|
195
|
+
* @param {Array} params - Parsed parameter nodes
|
|
196
|
+
* @returns {Object} Parsed configuration object
|
|
197
|
+
*/
|
|
198
|
+
const parseUtilityParams = (params) => {
|
|
199
|
+
const utilityParams = {
|
|
200
|
+
selector: null,
|
|
201
|
+
property: null,
|
|
202
|
+
scale: null,
|
|
203
|
+
generateAllCrossPairs: null,
|
|
150
204
|
};
|
|
151
205
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
currentValues = [];
|
|
177
|
-
} else {
|
|
178
|
-
const numValue = Number(value);
|
|
179
|
-
if (!isNaN(numValue)) currentValues.push(numValue);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (currentName && currentValues.length === 2) {
|
|
183
|
-
pairs[currentName] = currentValues;
|
|
206
|
+
for (let i = 0; i < params.length; i++) {
|
|
207
|
+
const param = params[i];
|
|
208
|
+
const nextParam = params[i + 1];
|
|
209
|
+
if (!param || !nextParam) continue;
|
|
210
|
+
const key = param.value;
|
|
211
|
+
let value = nextParam.value.replace(/[:,]/g, "");
|
|
212
|
+
|
|
213
|
+
switch (key) {
|
|
214
|
+
case "selector":
|
|
215
|
+
case "scale":
|
|
216
|
+
utilityParams[key] = value.replace(/['"]/g, "");
|
|
217
|
+
i++;
|
|
218
|
+
break;
|
|
219
|
+
case "property":
|
|
220
|
+
// Check if it's an array (next token is '[')
|
|
221
|
+
if (nextParam.value === "[") {
|
|
222
|
+
// It's an array - collect values until we hit ']'
|
|
223
|
+
const arrayValues = [];
|
|
224
|
+
i += 2; // Skip 'property' and '['
|
|
225
|
+
while (i < params.length && params[i].value !== "]") {
|
|
226
|
+
if (params[i].type === "string") {
|
|
227
|
+
arrayValues.push(params[i].value);
|
|
228
|
+
}
|
|
229
|
+
i++;
|
|
184
230
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
utilityParams.property = arrayValues;
|
|
232
|
+
} else {
|
|
233
|
+
// Single value
|
|
234
|
+
utilityParams.property = value.replace(/['"]/g, "");
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
case "generateAllCrossPairs":
|
|
239
|
+
utilityParams.generateAllCrossPairs = value === "true";
|
|
240
|
+
i++;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return utilityParams;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Processes @fluid at-rule and generates CSS custom properties
|
|
250
|
+
* @param {Object} atRule - PostCSS at-rule node
|
|
251
|
+
*/
|
|
252
|
+
const processFluidAtRule = (atRule) => {
|
|
253
|
+
const { nodes } = CSSValueParser(atRule.params);
|
|
254
|
+
const params = nodes[0].nodes.filter(
|
|
255
|
+
(x) =>
|
|
256
|
+
["word", "string"].includes(x.type) &&
|
|
257
|
+
x.value !== "{" &&
|
|
258
|
+
x.value !== "}",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const clampsParams = parseAtRuleParams(params);
|
|
262
|
+
clampsParams.pairs = extractPairs(params);
|
|
263
|
+
|
|
264
|
+
if (Object.keys(clampsParams.pairs).length === 0) {
|
|
265
|
+
throw new Error("[postcss-ruler] No pairs defined in @ruler scale()");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const clampPairs = Object.entries(clampsParams.pairs).map(
|
|
269
|
+
([name, values]) => ({ name, values }),
|
|
270
|
+
);
|
|
271
|
+
const clampScale = generateClamps({
|
|
272
|
+
...clampsParams,
|
|
273
|
+
pairs: clampPairs,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Store the scale for later use by utility classes
|
|
277
|
+
scales[clampsParams.prefix] = clampScale;
|
|
278
|
+
|
|
279
|
+
const postcss = require("postcss");
|
|
280
|
+
const root = postcss.root();
|
|
281
|
+
|
|
282
|
+
clampScale.forEach((step) => {
|
|
283
|
+
root.append(
|
|
284
|
+
postcss.decl({
|
|
285
|
+
prop: `--${clampsParams.prefix}-${step.label}`,
|
|
286
|
+
value: step.clamp,
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
atRule.replaceWith(root.nodes);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Processes @ruler utility() at-rule and generates utility classes
|
|
296
|
+
* @param {Object} atRule - PostCSS at-rule node
|
|
297
|
+
*/
|
|
298
|
+
const processUtilityAtRule = (atRule) => {
|
|
299
|
+
const postcss = require("postcss");
|
|
300
|
+
const { nodes } = CSSValueParser(atRule.params);
|
|
301
|
+
const params = nodes[0].nodes.filter(
|
|
302
|
+
(x) =>
|
|
303
|
+
["word", "string", "function"].includes(x.type) &&
|
|
304
|
+
x.value !== "{" &&
|
|
305
|
+
x.value !== "}",
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const utilityParams = parseUtilityParams(params);
|
|
309
|
+
|
|
310
|
+
// Validate required parameters
|
|
311
|
+
if (!utilityParams.selector) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
'[postcss-ruler] @ruler utility() requires a "selector" parameter',
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (!utilityParams.property) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
'[postcss-ruler] @ruler utility() requires a "property" parameter',
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (!utilityParams.scale) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
'[postcss-ruler] @ruler utility() requires a "scale" parameter',
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check if scale exists
|
|
328
|
+
const scale = scales[utilityParams.scale];
|
|
329
|
+
if (!scale) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`[postcss-ruler] Scale "${utilityParams.scale}" not found. Define it with @ruler scale() first.`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Determine which scale items to use
|
|
336
|
+
let scaleItems = scale;
|
|
337
|
+
if (utilityParams.generateAllCrossPairs === false) {
|
|
338
|
+
// Filter out cross-pairs (items with hyphens in label)
|
|
339
|
+
scaleItems = scale.filter((item) => !item.label.includes("-"));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Normalize property to array
|
|
343
|
+
const properties = Array.isArray(utilityParams.property)
|
|
344
|
+
? utilityParams.property
|
|
345
|
+
: [utilityParams.property];
|
|
346
|
+
|
|
347
|
+
// Generate utility classes as PostCSS nodes
|
|
348
|
+
const rules = scaleItems.map((item) => {
|
|
349
|
+
const selector = `${utilityParams.selector}-${item.label}`;
|
|
350
|
+
const rule = postcss.rule({ selector });
|
|
351
|
+
|
|
352
|
+
properties.forEach((prop) => {
|
|
353
|
+
rule.append(postcss.decl({ prop, value: item.clamp }));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return rule;
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
atRule.replaceWith(rules);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Processes inline fluid functions in declarations
|
|
364
|
+
* @param {Object} decl - PostCSS declaration node
|
|
365
|
+
*/
|
|
366
|
+
const processFluidDeclaration = (decl) => {
|
|
367
|
+
const regex = /ruler\.fluid\(([^)]+)\)/g;
|
|
368
|
+
let newValue = decl.value;
|
|
369
|
+
let match;
|
|
370
|
+
|
|
371
|
+
while ((match = regex.exec(decl.value)) !== null) {
|
|
372
|
+
const args = match[1]
|
|
373
|
+
.split(",")
|
|
374
|
+
.map((s) => s.trim())
|
|
375
|
+
.map(Number);
|
|
376
|
+
let [minSize, maxSize, minWidth, maxWidth] = args;
|
|
377
|
+
|
|
378
|
+
minWidth = minWidth || config.minWidth;
|
|
379
|
+
maxWidth = maxWidth || config.maxWidth;
|
|
380
|
+
|
|
381
|
+
if (!minSize || !maxSize) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
"[postcss-ruler] ruler.fluid() requires minSize and maxSize",
|
|
214
384
|
);
|
|
215
|
-
|
|
216
|
-
...clampsParams,
|
|
217
|
-
pairs: clampPairs,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const response = clampScale
|
|
221
|
-
.map(step => `--${clampsParams.prefix}-${step.label}: ${step.clamp};`)
|
|
222
|
-
.join('\n');
|
|
223
|
-
|
|
224
|
-
atRule.replaceWith(response);
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Processes inline fluid functions in declarations
|
|
229
|
-
* @param {Object} decl - PostCSS declaration node
|
|
230
|
-
*/
|
|
231
|
-
const processFluidDeclaration = decl => {
|
|
232
|
-
const regex = /ruler\.fluid\(([^)]+)\)/g;
|
|
233
|
-
let newValue = decl.value;
|
|
234
|
-
let match;
|
|
235
|
-
|
|
236
|
-
while ((match = regex.exec(decl.value)) !== null) {
|
|
237
|
-
const args = match[1].split(',').map(s => s.trim()).map(Number);
|
|
238
|
-
let [minSize, maxSize, minWidth, maxWidth] = args;
|
|
239
|
-
|
|
240
|
-
minWidth = minWidth || config.minWidth;
|
|
241
|
-
maxWidth = maxWidth || config.maxWidth;
|
|
242
|
-
|
|
243
|
-
if (!minSize || !maxSize) {
|
|
244
|
-
throw new Error(
|
|
245
|
-
'[postcss-ruler] ruler.fluid() requires minSize and maxSize'
|
|
246
|
-
);
|
|
247
|
-
}
|
|
385
|
+
}
|
|
248
386
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
387
|
+
const clampValue = calculateClamp({
|
|
388
|
+
minSize,
|
|
389
|
+
maxSize,
|
|
390
|
+
minWidth,
|
|
391
|
+
maxWidth,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
newValue = newValue.replace(match[0], clampValue);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (newValue !== decl.value) {
|
|
398
|
+
decl.value = newValue;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
postcssPlugin: "ruler",
|
|
404
|
+
AtRule: {
|
|
405
|
+
ruler: (atRule) => {
|
|
406
|
+
if (atRule.params.startsWith("scale(")) {
|
|
407
|
+
return processFluidAtRule(atRule);
|
|
408
|
+
} else if (atRule.params.startsWith("utility(")) {
|
|
409
|
+
return processUtilityAtRule(atRule);
|
|
261
410
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (atRule.params.startsWith('scale(')) {
|
|
269
|
-
return processFluidAtRule(atRule);
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
Declaration(decl) {
|
|
274
|
-
processFluidDeclaration(decl);
|
|
275
|
-
},
|
|
276
|
-
};
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
Declaration(decl) {
|
|
414
|
+
processFluidDeclaration(decl);
|
|
415
|
+
},
|
|
416
|
+
};
|
|
277
417
|
};
|
|
278
418
|
|
|
279
419
|
module.exports.postcss = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-ruler",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "PostCSS plugin to generate fluid scales and values.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"eslintConfig": {
|
|
39
39
|
"parserOptions": {
|
|
40
|
-
"ecmaVersion":
|
|
40
|
+
"ecmaVersion": 2018
|
|
41
41
|
},
|
|
42
42
|
"env": {
|
|
43
43
|
"node": true,
|