Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,15 @@ Checkout [tests] for more examples.

#### `precision` (default: `5`)

Allow you to define the precision for decimal numbers.
Allows you to define the precision for decimal numbers. Set it to `false` to
disable rounding.

```js
var out = postcss()
.use(calc({ precision: 10 }))
.process(css).css;
```

#### `preserve` (default: `false`)

Allow you to preserve calc() usage in output so browsers will handle decimal
precision themselves.

```js
var out = postcss()
.use(calc({ preserve: true }))
.process(css).css;
```

#### `warnWhenCannotResolve` (default: `false`)

Adds warnings when calc() are not reduced to a single value.
Expand All @@ -87,14 +77,34 @@ var out = postcss()

#### `mediaQueries` (default: `false`)

Allows calc() usage as part of media query declarations.
Allows calc() usage in media query parameters.

```js
var out = postcss()
.use(calc({ mediaQueries: true }))
.process(css).css;
```

Example:

```css
@media (min-width: calc(100px + 100px)) {
div {
width: 100px;
}
}
```

With `mediaQueries: true`, this becomes:

```css
@media (min-width: 200px) {
div {
width: 100px;
}
}
```

#### `selectors` (default: `false`)

Allows calc() usage as part of selectors.
Expand All @@ -108,11 +118,13 @@ var out = postcss()
Example:

```css
div[data-size='calc(3*3)'] {
div:nth-child(calc(1 + 2)) {
width: 100px;
}
```

With `selectors: true`, this becomes `div:nth-child(3)`.

#### `onParseError`

Callback invoked when a `calc()` body fails to parse or simplify. Matches
Expand Down
14 changes: 2 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
/**
* @typedef {object} PostCssCalcOptions
* @property {number | false} [precision]
* @property {boolean} [preserve]
* @property {boolean} [warnWhenCannotResolve]
* @property {boolean} [mediaQueries]
* @property {boolean} [selectors]
Expand Down Expand Up @@ -128,8 +127,7 @@ function transformValue(value, options, result, item) {

/**
* Runs `transformValue` over one text property of a decl/atrule/rule node
* and, per `options.preserve`, either updates it in place or inserts a
* clone carrying the transformed value ahead of the untouched original.
* and updates it in place.
* `setProp` closes over the property name and the concrete node type at
* each call site, since `Declaration`/`AtRule`/`Rule` don't share a typed
* "text property" name to index generically.
Expand All @@ -142,14 +140,7 @@ function transformValue(value, options, result, item) {
* @return {void}
*/
function applyTransform(node, current, setProp, options, result) {
const next = transformValue(current, options, result, node);
if (options.preserve && current !== next && node.parent) {
const clone = node.clone();
setProp(clone, next);
node.parent.insertBefore(node, clone);
} else {
setProp(node, next);
}
setProp(node, transformValue(current, options, result, node));
}

/**
Expand All @@ -160,7 +151,6 @@ function pluginCreator(opts) {
/** @type {ResolvedOptions} */
const options = {
precision: 5,
preserve: false,
warnWhenCannotResolve: false,
mediaQueries: false,
selectors: false,
Expand Down
7 changes: 0 additions & 7 deletions test/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -559,13 +559,6 @@ test(
)
);

test(
'should preserve the original declaration when `preserve` option is set to true',
testCss('foo{bar:calc(1rem * 1.5)}', 'foo{bar:1.5rem;bar:calc(1rem * 1.5)}', {
preserve: true,
})
);

test(
'should not yield warnings when nothing is wrong',
testValue('calc(500px - 0px)', '500px', { warnWhenCannotResolve: true })
Expand Down
37 changes: 10 additions & 27 deletions test/unit/plugin.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,10 @@ test('plugin: vendor-prefix wrapper preserved when expression cannot fully resol
const moz = await process('a{b:-moz-calc(1px + var(--x))}');
assert.equal(moz.css, 'a{b:-moz-calc(1px + var(--x))}');
});
// --- preserve option -----------------------------------------------------
test('plugin: preserve clones the original decl alongside the simplified one', async () => {
// --- obsolete JavaScript options ----------------------------------------
test('plugin: obsolete preserve option is ignored at runtime', async () => {
const { css } = await process('a{b:calc(1px + 2px)}', { preserve: true });
assert.equal(css, 'a{b:3px;b:calc(1px + 2px)}');
});
test('plugin: preserve is a no-op when value is unchanged', async () => {
const { css } = await process('a{b:red}', { preserve: true });
assert.equal(css, 'a{b:red}');
assert.equal(css, 'a{b:3px}');
});
// --- warnWhenCannotResolve -----------------------------------------------
test('plugin: warnWhenCannotResolve surfaces unresolved expressions', async () => {
Expand Down Expand Up @@ -101,15 +97,12 @@ test('plugin: mediaQueries off leaves @media untouched', async () => {
);
assert.match(css, /calc\(100px \+ 100px\)/);
});
test('plugin: mediaQueries + preserve clones the @media rule', async () => {
// Both options together: the simplified atrule appears, followed by
// the original (preserve clones into the parent before the live node).
test('plugin: mediaQueries transforms the @media rule in place', async () => {
const { css } = await process(
'@media (min-width: calc(100px + 100px)) { a{b:c} }',
{ mediaQueries: true, preserve: true }
{ mediaQueries: true }
);
assert.match(css, /min-width: 200px/);
assert.match(css, /calc\(100px \+ 100px\)/);
assert.equal(css, '@media (min-width: 200px) { a{b:c} }');
});
// --- onParseError --------------------------------------------------------
test('plugin: default behavior on parse failure is a PostCSS warn', async () => {
Expand Down Expand Up @@ -159,16 +152,12 @@ test('plugin: precision 0 rounds to whole numbers', async () => {
assert.equal(css, 'a{b:1in}');
});
// --- Option combinations -------------------------------------------------
test('plugin: preserve + warnWhenCannotResolve — both fire together', async () => {
// Unresolved expression gets preserved AND warned about.
test('plugin: obsolete preserve option and warnWhenCannotResolve work together', async () => {
const { css, warnings } = await process('a{b:calc(100% + var(--x))}', {
preserve: true,
warnWhenCannotResolve: true,
});
// preserve clones the original alongside — both have the same
// serialized value since the simplifier can't fully reduce this
// expression, but the clone step still runs.
assert.match(css, /calc\(100% \+ var\(--x\)\)/);
assert.equal(css, 'a{b:calc(100% + var(--x))}');
assert.equal(warnings.length, 1);
});
test('plugin: onParseError catches errors in @media params (mediaQueries: true)', async () => {
Expand All @@ -189,16 +178,11 @@ test('plugin: selectors:true reduces calc() in selector text', async () => {
});
assert.match(css, /:nth-child\(3\)/);
});
test('plugin: selectors + preserve clones the rule', async () => {
// Same shape as the mediaQueries + preserve case: the simplified rule
// appears first, followed by the original (preserve clones into the
// parent before the live node).
test('plugin: selectors transforms the rule in place', async () => {
const { css } = await process('a:nth-child(calc(1 + 2)) { b: c }', {
selectors: true,
preserve: true,
});
assert.match(css, /:nth-child\(3\)/);
assert.match(css, /:nth-child\(calc\(1 \+ 2\)\)/);
assert.equal(css, 'a:nth-child(3) { b: c }');
});
test('plugin: onParseError does not fire for fully-resolved inputs', async () => {
const errors = [];
Expand All @@ -210,7 +194,6 @@ test('plugin: onParseError does not fire for fully-resolved inputs', async () =>
test('plugin: options are no-ops on values with no calc()', async () => {
// Every option branch should harmlessly ignore non-calc declarations.
const { css, warnings } = await process('a{color:red;padding:10px 20px}', {
preserve: true,
warnWhenCannotResolve: true,
mediaQueries: true,
selectors: true,
Expand Down
1 change: 0 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export type PostCssCalcOptions = {
precision?: number | false;
preserve?: boolean;
warnWhenCannotResolve?: boolean;
mediaQueries?: boolean;
selectors?: boolean;
Expand Down