postcss-ruler 1.1.0 → 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 +115 -47
- package/index.js +398 -392
- package/package.json +1 -1
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,17 +24,18 @@ 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
|
|
@@ -40,6 +43,7 @@ module.exports = {
|
|
|
40
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,12 +101,13 @@ 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 */
|
|
@@ -143,27 +151,41 @@ Generate utility classes from your defined scales with complete selector flexibi
|
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
**Generates:**
|
|
154
|
+
|
|
146
155
|
```css
|
|
147
156
|
--space-xs: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
148
157
|
--space-sm: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
149
158
|
--space-md: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
150
159
|
|
|
151
|
-
.gap-xs {
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
}
|
|
154
169
|
|
|
155
|
-
&.active-xs {
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
158
179
|
|
|
159
180
|
.p-block-xs {
|
|
160
181
|
padding-top: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
161
|
-
padding-bottom: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem)
|
|
182
|
+
padding-bottom: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
162
183
|
}
|
|
163
184
|
/* ... */
|
|
164
185
|
```
|
|
165
186
|
|
|
166
187
|
**Supported selector patterns:**
|
|
188
|
+
|
|
167
189
|
- **Class selectors**: `.gap` → `.gap-xs`, `.gap-sm`, `.gap-md`
|
|
168
190
|
- **Nested with &**: `&.active` → `&.active-xs`, `&.active-sm` (PostCSS nesting)
|
|
169
191
|
- **Multiple classes**: `.container.space` → `.container.space-xs`, etc.
|
|
@@ -173,16 +195,17 @@ Generate utility classes from your defined scales with complete selector flexibi
|
|
|
173
195
|
|
|
174
196
|
**Utility options:**
|
|
175
197
|
|
|
176
|
-
| Option
|
|
177
|
-
|
|
178
|
-
| `selector`
|
|
179
|
-
| `property`
|
|
180
|
-
| `scale`
|
|
181
|
-
| `generateAllCrossPairs` | boolean
|
|
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) |
|
|
182
204
|
|
|
183
205
|
### 3. Inline Mode: Fluid Function
|
|
184
206
|
|
|
185
207
|
Convert individual values directly to `clamp()` functions:
|
|
208
|
+
|
|
186
209
|
```css
|
|
187
210
|
.element {
|
|
188
211
|
/* Uses default minWidth/maxWidth from config */
|
|
@@ -197,11 +220,13 @@ Convert individual values directly to `clamp()` functions:
|
|
|
197
220
|
```
|
|
198
221
|
|
|
199
222
|
**Generates:**
|
|
223
|
+
|
|
200
224
|
```css
|
|
201
225
|
.element {
|
|
202
226
|
font-size: clamp(1rem, 0.4545vw + 0.8636rem, 1.5rem);
|
|
203
227
|
padding: clamp(0.75rem, 0.9091vw + 0.4773rem, 1.25rem);
|
|
204
|
-
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);
|
|
205
230
|
}
|
|
206
231
|
```
|
|
207
232
|
|
|
@@ -209,23 +234,23 @@ Convert individual values directly to `clamp()` functions:
|
|
|
209
234
|
|
|
210
235
|
### Plugin Options
|
|
211
236
|
|
|
212
|
-
| Option
|
|
213
|
-
|
|
214
|
-
| `minWidth`
|
|
215
|
-
| `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 |
|
|
216
241
|
| `generateAllCrossPairs` | boolean | `false` | Generate cross-combinations in scale mode |
|
|
217
242
|
|
|
218
243
|
### At-Rule Options
|
|
219
244
|
|
|
220
245
|
All options can be overridden per `@ruler scale()` declaration:
|
|
221
246
|
|
|
222
|
-
| Option
|
|
223
|
-
|
|
224
|
-
| `minWidth`
|
|
225
|
-
| `maxWidth`
|
|
226
|
-
| `prefix`
|
|
227
|
-
| `generateAllCrossPairs` | boolean | `false`
|
|
228
|
-
| `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]` |
|
|
229
254
|
|
|
230
255
|
### Inline Function Syntax
|
|
231
256
|
|
|
@@ -233,21 +258,62 @@ All options can be overridden per `@ruler scale()` declaration:
|
|
|
233
258
|
ruler.fluid(minSize, maxSize[, minWidth, maxWidth])
|
|
234
259
|
```
|
|
235
260
|
|
|
236
|
-
| Parameter
|
|
237
|
-
|
|
238
|
-
| `minSize`
|
|
239
|
-
| `maxSize`
|
|
240
|
-
| `minWidth` | number | No
|
|
241
|
-
| `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.
|
|
242
308
|
|
|
243
309
|
### Utility Options
|
|
244
310
|
|
|
245
|
-
| Option
|
|
246
|
-
|
|
247
|
-
| `selector`
|
|
248
|
-
| `property`
|
|
249
|
-
| `scale`
|
|
250
|
-
| `generateAllCrossPairs` | boolean
|
|
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) |
|
|
251
317
|
|
|
252
318
|
## How It Works
|
|
253
319
|
|
|
@@ -319,6 +385,7 @@ Generate a complete set of utility classes from your design system:
|
|
|
319
385
|
```
|
|
320
386
|
|
|
321
387
|
Use in your HTML:
|
|
388
|
+
|
|
322
389
|
```html
|
|
323
390
|
<section class="p-lg gap-md">
|
|
324
391
|
<div class="stack gap-sm">
|
|
@@ -399,6 +466,7 @@ h2 {
|
|
|
399
466
|
## Browser Support
|
|
400
467
|
|
|
401
468
|
The `clamp()` function is supported in all modern browsers:
|
|
469
|
+
|
|
402
470
|
- Chrome 79+
|
|
403
471
|
- Firefox 75+
|
|
404
472
|
- Safari 13.1+
|
package/index.js
CHANGED
|
@@ -1,413 +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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
71
85
|
minWidth,
|
|
72
86
|
maxWidth,
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
|
|
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}`,
|
|
77
99
|
clamp: calculateClamp({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
minSize: smaller.values[0],
|
|
101
|
+
maxSize: larger.values[1],
|
|
102
|
+
minWidth,
|
|
103
|
+
maxWidth,
|
|
82
104
|
}),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (generateAllCrossPairs) {
|
|
86
|
-
let crossPairs = [];
|
|
87
|
-
for (let i = 0; i < pairs.length; i++) {
|
|
88
|
-
for (let j = i + 1; j < pairs.length; j++) {
|
|
89
|
-
const [smaller, larger] = [pairs[i], pairs[j]].sort(
|
|
90
|
-
(a, b) => a.values[0] - b.values[0]
|
|
91
|
-
);
|
|
92
|
-
crossPairs.push({
|
|
93
|
-
label: `${smaller.name}-${larger.name}`,
|
|
94
|
-
clamp: calculateClamp({
|
|
95
|
-
minSize: smaller.values[0],
|
|
96
|
-
maxSize: larger.values[1],
|
|
97
|
-
minWidth,
|
|
98
|
-
maxWidth,
|
|
99
|
-
}),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
clampScales = [...clampScales, ...crossPairs];
|
|
105
|
+
});
|
|
104
106
|
}
|
|
105
|
-
|
|
106
|
-
|
|
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,
|
|
107
126
|
};
|
|
108
127
|
|
|
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
|
-
|
|
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;
|
|
145
177
|
}
|
|
146
|
-
|
|
147
|
-
|
|
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,
|
|
148
204
|
};
|
|
149
205
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
currentValues = [];
|
|
175
|
-
} else {
|
|
176
|
-
const numValue = Number(value);
|
|
177
|
-
if (!isNaN(numValue)) currentValues.push(numValue);
|
|
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++;
|
|
178
230
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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",
|
|
303
384
|
);
|
|
385
|
+
}
|
|
304
386
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
`[postcss-ruler] Scale "${utilityParams.scale}" not found. Define it with @ruler scale() first.`
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Determine which scale items to use
|
|
333
|
-
let scaleItems = scale;
|
|
334
|
-
if (utilityParams.generateAllCrossPairs === false) {
|
|
335
|
-
// Filter out cross-pairs (items with hyphens in label)
|
|
336
|
-
scaleItems = scale.filter(item => !item.label.includes('-'));
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Normalize property to array
|
|
340
|
-
const properties = Array.isArray(utilityParams.property)
|
|
341
|
-
? utilityParams.property
|
|
342
|
-
: [utilityParams.property];
|
|
343
|
-
|
|
344
|
-
// Generate utility classes as PostCSS nodes
|
|
345
|
-
const rules = scaleItems.map(item => {
|
|
346
|
-
const selector = `${utilityParams.selector}-${item.label}`;
|
|
347
|
-
const rule = postcss.rule({ selector });
|
|
348
|
-
|
|
349
|
-
properties.forEach(prop => {
|
|
350
|
-
rule.append(postcss.decl({ prop, value: item.clamp }));
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
return rule;
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
atRule.replaceWith(rules);
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Processes inline fluid functions in declarations
|
|
361
|
-
* @param {Object} decl - PostCSS declaration node
|
|
362
|
-
*/
|
|
363
|
-
const processFluidDeclaration = decl => {
|
|
364
|
-
const regex = /ruler\.fluid\(([^)]+)\)/g;
|
|
365
|
-
let newValue = decl.value;
|
|
366
|
-
let match;
|
|
367
|
-
|
|
368
|
-
while ((match = regex.exec(decl.value)) !== null) {
|
|
369
|
-
const args = match[1].split(',').map(s => s.trim()).map(Number);
|
|
370
|
-
let [minSize, maxSize, minWidth, maxWidth] = args;
|
|
371
|
-
|
|
372
|
-
minWidth = minWidth || config.minWidth;
|
|
373
|
-
maxWidth = maxWidth || config.maxWidth;
|
|
374
|
-
|
|
375
|
-
if (!minSize || !maxSize) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
'[postcss-ruler] ruler.fluid() requires minSize and maxSize'
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const clampValue = calculateClamp({
|
|
382
|
-
minSize,
|
|
383
|
-
maxSize,
|
|
384
|
-
minWidth,
|
|
385
|
-
maxWidth,
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
newValue = newValue.replace(match[0], clampValue);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (newValue !== decl.value) {
|
|
392
|
-
decl.value = newValue;
|
|
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);
|
|
393
410
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (atRule.params.startsWith('scale(')) {
|
|
401
|
-
return processFluidAtRule(atRule);
|
|
402
|
-
} else if (atRule.params.startsWith('utility(')) {
|
|
403
|
-
return processUtilityAtRule(atRule);
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
Declaration(decl) {
|
|
408
|
-
processFluidDeclaration(decl);
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
Declaration(decl) {
|
|
414
|
+
processFluidDeclaration(decl);
|
|
415
|
+
},
|
|
416
|
+
};
|
|
411
417
|
};
|
|
412
418
|
|
|
413
419
|
module.exports.postcss = true;
|