postcss-ruler 1.3.0 → 1.4.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 +105 -11
- package/index.js +33 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -230,15 +230,108 @@ Convert individual values directly to `clamp()` functions:
|
|
|
230
230
|
}
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
+
### 4. Low Specificity Mode: Zero-Specificity Utilities
|
|
234
|
+
|
|
235
|
+
Wrap generated selectors in `:where()` to reduce their specificity to 0, making them easier to override:
|
|
236
|
+
|
|
237
|
+
```css
|
|
238
|
+
@ruler scale({
|
|
239
|
+
prefix: 'space',
|
|
240
|
+
pairs: {
|
|
241
|
+
"xs": [8, 16],
|
|
242
|
+
"sm": [16, 24]
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
/* Enable lowSpecificity per utility */
|
|
247
|
+
@ruler utility({
|
|
248
|
+
selector: '.gap',
|
|
249
|
+
property: 'gap',
|
|
250
|
+
scale: 'space',
|
|
251
|
+
lowSpecificity: true
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Generates:**
|
|
256
|
+
|
|
257
|
+
```css
|
|
258
|
+
--space-xs: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
259
|
+
--space-sm: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
260
|
+
|
|
261
|
+
:where(.gap-xs) {
|
|
262
|
+
gap: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
263
|
+
}
|
|
264
|
+
:where(.gap-sm) {
|
|
265
|
+
gap: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Specificity comparison:**
|
|
270
|
+
|
|
271
|
+
- `.gap-xs` → specificity (0,1,0)
|
|
272
|
+
- `:where(.gap-xs)` → specificity (0,0,0)
|
|
273
|
+
|
|
274
|
+
**Use case:** Design system utilities that should be easily overridable without `!important`:
|
|
275
|
+
|
|
276
|
+
```css
|
|
277
|
+
/* Utility with zero specificity */
|
|
278
|
+
:where(.gap-md) {
|
|
279
|
+
gap: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* Easy to override with a simple class */
|
|
283
|
+
.custom-layout {
|
|
284
|
+
gap: 2rem; /* This wins without !important */
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Works with attribute mode:**
|
|
289
|
+
|
|
290
|
+
```css
|
|
291
|
+
@ruler utility({
|
|
292
|
+
attribute: 'data-size',
|
|
293
|
+
property: 'font-size',
|
|
294
|
+
scale: 'size',
|
|
295
|
+
lowSpecificity: true
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Generates:**
|
|
300
|
+
|
|
301
|
+
```css
|
|
302
|
+
:where([data-size="xs"]) {
|
|
303
|
+
font-size: var(--size-xs);
|
|
304
|
+
}
|
|
305
|
+
:where([data-size="sm"]) {
|
|
306
|
+
font-size: var(--size-sm);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Global configuration:**
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
// postcss.config.js
|
|
314
|
+
module.exports = {
|
|
315
|
+
plugins: {
|
|
316
|
+
"postcss-ruler": {
|
|
317
|
+
lowSpecificity: true, // All utilities use :where() by default
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
You can override the global setting per utility by explicitly setting `lowSpecificity: false`.
|
|
324
|
+
|
|
233
325
|
## Configuration Options
|
|
234
326
|
|
|
235
327
|
### Plugin Options
|
|
236
328
|
|
|
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
|
|
241
|
-
| `generateAllCrossPairs` | boolean | `false` | Generate cross-combinations in scale mode
|
|
329
|
+
| Option | Type | Default | Description |
|
|
330
|
+
| ----------------------- | ------- | ------- | -------------------------------------------------------------- |
|
|
331
|
+
| `minWidth` | number | `320` | Default minimum viewport width in pixels |
|
|
332
|
+
| `maxWidth` | number | `1760` | Default maximum viewport width in pixels |
|
|
333
|
+
| `generateAllCrossPairs` | boolean | `false` | Generate cross-combinations in scale mode |
|
|
334
|
+
| `lowSpecificity` | boolean | `false` | Wrap utility selectors in `:where()` to lower specificity to 0 |
|
|
242
335
|
|
|
243
336
|
### At-Rule Options
|
|
244
337
|
|
|
@@ -308,12 +401,13 @@ When min and max values are equal, postcss-ruler outputs a simple rem value inst
|
|
|
308
401
|
|
|
309
402
|
### Utility Options
|
|
310
403
|
|
|
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)
|
|
404
|
+
| Option | Type | Default | Description |
|
|
405
|
+
| ----------------------- | --------------- | -------- | --------------------------------------------------------------------------------- |
|
|
406
|
+
| `selector` | string | required | Any valid CSS selector pattern (e.g., `.gap`, `&.active`, `#section`) |
|
|
407
|
+
| `property` | string or array | required | CSS property name(s) to apply the scale values to |
|
|
408
|
+
| `scale` | string | required | Name of a previously defined scale (the `prefix` value) |
|
|
409
|
+
| `generateAllCrossPairs` | boolean | No | Include/exclude cross-pairs (overrides scale default) |
|
|
410
|
+
| `lowSpecificity` | boolean | No | Wrap selectors in `:where()` to reduce specificity to 0 (overrides global config) |
|
|
317
411
|
|
|
318
412
|
## How It Works
|
|
319
413
|
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ module.exports = (opts) => {
|
|
|
8
8
|
minWidth: 320,
|
|
9
9
|
maxWidth: 1760,
|
|
10
10
|
generateAllCrossPairs: false,
|
|
11
|
+
lowSpecificity: false,
|
|
11
12
|
};
|
|
12
13
|
const config = Object.assign(DEFAULTS, opts);
|
|
13
14
|
|
|
@@ -202,6 +203,7 @@ module.exports = (opts) => {
|
|
|
202
203
|
scale: null,
|
|
203
204
|
generateAllCrossPairs: null,
|
|
204
205
|
attribute: null,
|
|
206
|
+
lowSpecificity: null,
|
|
205
207
|
};
|
|
206
208
|
|
|
207
209
|
for (let i = 0; i < params.length; i++) {
|
|
@@ -241,6 +243,10 @@ module.exports = (opts) => {
|
|
|
241
243
|
utilityParams.generateAllCrossPairs = value === "true";
|
|
242
244
|
i++;
|
|
243
245
|
break;
|
|
246
|
+
case "lowSpecificity":
|
|
247
|
+
utilityParams.lowSpecificity = value === "true";
|
|
248
|
+
i++;
|
|
249
|
+
break;
|
|
244
250
|
}
|
|
245
251
|
}
|
|
246
252
|
|
|
@@ -309,16 +315,21 @@ module.exports = (opts) => {
|
|
|
309
315
|
|
|
310
316
|
const utilityParams = parseUtilityParams(params);
|
|
311
317
|
|
|
318
|
+
// Resolve lowSpecificity from config if not explicitly set
|
|
319
|
+
if (utilityParams.lowSpecificity === null) {
|
|
320
|
+
utilityParams.lowSpecificity = config.lowSpecificity;
|
|
321
|
+
}
|
|
322
|
+
|
|
312
323
|
// Validate attribute-specific constraints first
|
|
313
324
|
if (utilityParams.attribute !== null) {
|
|
314
325
|
if (utilityParams.attribute === "") {
|
|
315
326
|
throw new Error(
|
|
316
|
-
|
|
327
|
+
"[postcss-ruler] @ruler utility() attribute parameter cannot be empty",
|
|
317
328
|
);
|
|
318
329
|
}
|
|
319
330
|
if (!/^[a-zA-Z0-9_-]+$/.test(utilityParams.attribute)) {
|
|
320
331
|
throw new Error(
|
|
321
|
-
|
|
332
|
+
"[postcss-ruler] @ruler utility() attribute parameter must contain only letters, numbers, hyphens, and underscores",
|
|
322
333
|
);
|
|
323
334
|
}
|
|
324
335
|
}
|
|
@@ -368,13 +379,31 @@ module.exports = (opts) => {
|
|
|
368
379
|
if (utilityParams.attribute) {
|
|
369
380
|
// Attribute mode: [data-attr="value"] or .class[data-attr="value"]
|
|
370
381
|
const attrSelector = `[${utilityParams.attribute}="${item.label}"]`;
|
|
371
|
-
|
|
382
|
+
const baseSelector = utilityParams.selector
|
|
372
383
|
? `${utilityParams.selector}${attrSelector}`
|
|
373
384
|
: attrSelector;
|
|
385
|
+
ruleSelector = utilityParams.lowSpecificity
|
|
386
|
+
? `:where(${baseSelector})`
|
|
387
|
+
: baseSelector;
|
|
374
388
|
ruleValue = `var(--${utilityParams.scale}-${item.label})`;
|
|
375
389
|
} else {
|
|
376
390
|
// Class mode (existing behavior)
|
|
377
|
-
|
|
391
|
+
const baseSelector = `${utilityParams.selector}-${item.label}`;
|
|
392
|
+
|
|
393
|
+
// Handle parent context selectors (e.g., ".container &")
|
|
394
|
+
if (utilityParams.lowSpecificity) {
|
|
395
|
+
if (utilityParams.selector.endsWith(" &")) {
|
|
396
|
+
// Parent context: ".container &" -> ".container :where(&-xs)"
|
|
397
|
+
const parentPart = utilityParams.selector.slice(0, -1); // Remove trailing "&"
|
|
398
|
+
ruleSelector = `${parentPart}:where(&-${item.label})`;
|
|
399
|
+
} else {
|
|
400
|
+
// Regular selector: wrap entire selector
|
|
401
|
+
ruleSelector = `:where(${baseSelector})`;
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
ruleSelector = baseSelector;
|
|
405
|
+
}
|
|
406
|
+
|
|
378
407
|
ruleValue = item.clamp;
|
|
379
408
|
}
|
|
380
409
|
|