mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-27 12:44:42 +01:00
commit
244a1d9cb8
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,3 +1,32 @@
|
||||
## 1.25.0
|
||||
|
||||
* Add functions to the built-in "sass:math" module.
|
||||
|
||||
* `clamp($min, $number, $max)`. Clamps `$number` in between `$min` and `$max`.
|
||||
|
||||
* `hypot($numbers...)`. Given *n* numbers, outputs the length of the
|
||||
*n*-dimensional vector that has components equal to each of the inputs.
|
||||
|
||||
* Exponential. All inputs must be unitless.
|
||||
* `log($number)` or `log($number, $base)`. If no base is provided, performs
|
||||
a natural log.
|
||||
* `pow($base, $exponent)`
|
||||
* `sqrt($number)`
|
||||
|
||||
* Trigonometric. The input must be an angle. If no unit is given, the input is
|
||||
assumed to be in `rad`.
|
||||
* `cos($number)`
|
||||
* `sin($number)`
|
||||
* `tan($number)`
|
||||
|
||||
* Inverse trigonometric. The output is in `deg`.
|
||||
* `acos($number)`. Input must be unitless.
|
||||
* `asin($number)`. Input must be unitless.
|
||||
* `atan($number)`. Input must be unitless.
|
||||
* `atan2($y, $x)`. `$y` and `$x` must have compatible units or be unitless.
|
||||
|
||||
* Add the variables `$pi` and `$e` to the built-in "sass:math" module.
|
||||
|
||||
## 1.24.5
|
||||
|
||||
* Highlight contextually-relevant sections of the stylesheet in error messages,
|
||||
|
@ -13,34 +13,51 @@ import '../module/built_in.dart';
|
||||
import '../util/number.dart';
|
||||
import '../value.dart';
|
||||
|
||||
/// A random number generator.
|
||||
final _random = math.Random();
|
||||
|
||||
/// The global definitions of Sass math functions.
|
||||
final global = UnmodifiableListView([
|
||||
_round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit, //
|
||||
_percentage,
|
||||
_abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round,
|
||||
_unit, //
|
||||
_compatible.withName("comparable"),
|
||||
_isUnitless.withName("unitless"),
|
||||
_compatible.withName("comparable")
|
||||
]);
|
||||
|
||||
/// The Sass math module.
|
||||
final module = BuiltInModule("math", functions: [
|
||||
_round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit,
|
||||
_isUnitless, //
|
||||
_percentage, _compatible
|
||||
]);
|
||||
|
||||
final _percentage = _function("percentage", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
number.assertNoUnits("number");
|
||||
return SassNumber(number.value * 100, '%');
|
||||
_abs, _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, //
|
||||
_floor, _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, //
|
||||
_randomFunction, _round, _sin, _sqrt, _tan, _unit,
|
||||
], variables: {
|
||||
"e": SassNumber(math.e),
|
||||
"pi": SassNumber(math.pi),
|
||||
});
|
||||
|
||||
final _round = _numberFunction("round", fuzzyRound);
|
||||
///
|
||||
/// Bounding functions
|
||||
///
|
||||
|
||||
final _ceil = _numberFunction("ceil", (value) => value.ceil());
|
||||
|
||||
final _clamp = _function("clamp", r"$min, $number, $max", (arguments) {
|
||||
var min = arguments[0].assertNumber("min");
|
||||
var number = arguments[1].assertNumber("number");
|
||||
var max = arguments[2].assertNumber("max");
|
||||
if (min.hasUnits == number.hasUnits && number.hasUnits == max.hasUnits) {
|
||||
if (min.greaterThanOrEquals(max).isTruthy) return min;
|
||||
if (min.greaterThanOrEquals(number).isTruthy) return min;
|
||||
if (number.greaterThanOrEquals(max).isTruthy) return max;
|
||||
return number;
|
||||
}
|
||||
|
||||
var arg2 = min.hasUnits != number.hasUnits ? number : max;
|
||||
var arg2Name = min.hasUnits != number.hasUnits ? "\$number" : "\$max";
|
||||
var unit1 = min.hasUnits ? "has unit ${min.unitString}" : "is unitless";
|
||||
var unit2 = arg2.hasUnits ? "has unit ${arg2.unitString}" : "is unitless";
|
||||
throw SassScriptException(
|
||||
"\$min $unit1 but $arg2Name $unit2. Arguments must all have units or all "
|
||||
"be unitless.");
|
||||
});
|
||||
|
||||
final _floor = _numberFunction("floor", (value) => value.floor());
|
||||
final _abs = _numberFunction("abs", (value) => value.abs());
|
||||
|
||||
final _max = _function("max", r"$numbers...", (arguments) {
|
||||
SassNumber max;
|
||||
@ -62,6 +79,231 @@ final _min = _function("min", r"$numbers...", (arguments) {
|
||||
throw SassScriptException("At least one argument must be passed.");
|
||||
});
|
||||
|
||||
final _round = _numberFunction("round", fuzzyRound);
|
||||
|
||||
///
|
||||
/// Distance functions
|
||||
///
|
||||
|
||||
final _abs = _numberFunction("abs", (value) => value.abs());
|
||||
|
||||
final _hypot = _function("hypot", r"$numbers...", (arguments) {
|
||||
var numbers =
|
||||
arguments[0].asList.map((argument) => argument.assertNumber()).toList();
|
||||
if (numbers.isEmpty) {
|
||||
throw SassScriptException("At least one argument must be passed.");
|
||||
}
|
||||
|
||||
var numeratorUnits = numbers[0].numeratorUnits;
|
||||
var denominatorUnits = numbers[0].denominatorUnits;
|
||||
var subtotal = 0.0;
|
||||
for (var i = 0; i < numbers.length; i++) {
|
||||
var number = numbers[i];
|
||||
if (number.hasUnits == numbers[0].hasUnits) {
|
||||
number = number.coerce(numeratorUnits, denominatorUnits);
|
||||
subtotal += math.pow(number.value, 2);
|
||||
} else {
|
||||
var unit1 = numbers[0].hasUnits
|
||||
? "has unit ${numbers[0].unitString}"
|
||||
: "is unitless";
|
||||
var unit2 =
|
||||
number.hasUnits ? "has unit ${number.unitString}" : "is unitless";
|
||||
throw SassScriptException(
|
||||
"Argument 1 $unit1 but argument ${i + 1} $unit2. Arguments must all "
|
||||
"have units or all be unitless.");
|
||||
}
|
||||
}
|
||||
return SassNumber.withUnits(math.sqrt(subtotal),
|
||||
numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits);
|
||||
});
|
||||
|
||||
///
|
||||
/// Exponential functions
|
||||
///
|
||||
|
||||
final _log = _function("log", r"$number, $base: null", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
if (number.hasUnits) {
|
||||
throw SassScriptException("\$number: Expected $number to have no units.");
|
||||
}
|
||||
|
||||
var numberValue = _fuzzyRoundIfZero(number.value);
|
||||
if (arguments[1] == sassNull) return SassNumber(math.log(numberValue));
|
||||
|
||||
var base = arguments[1].assertNumber("base");
|
||||
if (base.hasUnits) {
|
||||
throw SassScriptException("\$base: Expected $base to have no units.");
|
||||
}
|
||||
|
||||
var baseValue = fuzzyEquals(base.value, 1)
|
||||
? fuzzyRound(base.value)
|
||||
: _fuzzyRoundIfZero(base.value);
|
||||
return SassNumber(math.log(numberValue) / math.log(baseValue));
|
||||
});
|
||||
|
||||
final _pow = _function("pow", r"$base, $exponent", (arguments) {
|
||||
var base = arguments[0].assertNumber("base");
|
||||
var exponent = arguments[1].assertNumber("exponent");
|
||||
if (base.hasUnits) {
|
||||
throw SassScriptException("\$base: Expected $base to have no units.");
|
||||
} else if (exponent.hasUnits) {
|
||||
throw SassScriptException(
|
||||
"\$exponent: Expected $exponent to have no units.");
|
||||
}
|
||||
|
||||
// Exponentiating certain real numbers leads to special behaviors. Ensure that
|
||||
// these behaviors are consistent for numbers within the precision limit.
|
||||
var baseValue = _fuzzyRoundIfZero(base.value);
|
||||
var exponentValue = _fuzzyRoundIfZero(exponent.value);
|
||||
if (fuzzyEquals(baseValue.abs(), 1) && exponentValue.isInfinite) {
|
||||
return SassNumber(double.nan);
|
||||
} else if (fuzzyEquals(baseValue, 0)) {
|
||||
if (exponentValue.isFinite &&
|
||||
fuzzyIsInt(exponentValue) &&
|
||||
fuzzyAsInt(exponentValue) % 2 == 1) {
|
||||
exponentValue = fuzzyRound(exponentValue);
|
||||
}
|
||||
} else if (baseValue.isFinite &&
|
||||
fuzzyLessThan(baseValue, 0) &&
|
||||
exponentValue.isFinite &&
|
||||
fuzzyIsInt(exponentValue)) {
|
||||
exponentValue = fuzzyRound(exponentValue);
|
||||
} else if (baseValue.isInfinite &&
|
||||
fuzzyLessThan(baseValue, 0) &&
|
||||
exponentValue.isFinite &&
|
||||
fuzzyIsInt(exponentValue) &&
|
||||
fuzzyAsInt(exponentValue) % 2 == 1) {
|
||||
exponentValue = fuzzyRound(exponentValue);
|
||||
}
|
||||
return SassNumber(math.pow(baseValue, exponentValue));
|
||||
});
|
||||
|
||||
final _sqrt = _function("sqrt", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
if (number.hasUnits) {
|
||||
throw SassScriptException("\$number: Expected $number to have no units.");
|
||||
}
|
||||
|
||||
var numberValue = _fuzzyRoundIfZero(number.value);
|
||||
return SassNumber(math.sqrt(numberValue));
|
||||
});
|
||||
|
||||
///
|
||||
/// Trigonometric functions
|
||||
///
|
||||
|
||||
final _acos = _function("acos", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
if (number.hasUnits) {
|
||||
throw SassScriptException("\$number: Expected $number to have no units.");
|
||||
}
|
||||
|
||||
var numberValue = fuzzyEquals(number.value.abs(), 1)
|
||||
? fuzzyRound(number.value)
|
||||
: number.value;
|
||||
var acos = math.acos(numberValue) * 180 / math.pi;
|
||||
return SassNumber.withUnits(acos, numeratorUnits: ['deg']);
|
||||
});
|
||||
|
||||
final _asin = _function("asin", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
if (number.hasUnits) {
|
||||
throw SassScriptException("\$number: Expected $number to have no units.");
|
||||
}
|
||||
|
||||
var numberValue = fuzzyEquals(number.value.abs(), 1)
|
||||
? fuzzyRound(number.value)
|
||||
: _fuzzyRoundIfZero(number.value);
|
||||
var asin = math.asin(numberValue) * 180 / math.pi;
|
||||
return SassNumber.withUnits(asin, numeratorUnits: ['deg']);
|
||||
});
|
||||
|
||||
final _atan = _function("atan", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
if (number.hasUnits) {
|
||||
throw SassScriptException("\$number: Expected $number to have no units.");
|
||||
}
|
||||
|
||||
var numberValue = _fuzzyRoundIfZero(number.value);
|
||||
var atan = math.atan(numberValue) * 180 / math.pi;
|
||||
return SassNumber.withUnits(atan, numeratorUnits: ['deg']);
|
||||
});
|
||||
|
||||
final _atan2 = _function("atan2", r"$y, $x", (arguments) {
|
||||
var y = arguments[0].assertNumber("y");
|
||||
var x = arguments[1].assertNumber("x");
|
||||
if (y.hasUnits != x.hasUnits) {
|
||||
var unit1 = y.hasUnits ? "has unit ${y.unitString}" : "is unitless";
|
||||
var unit2 = x.hasUnits ? "has unit ${x.unitString}" : "is unitless";
|
||||
throw SassScriptException(
|
||||
"\$y $unit1 but \$x $unit2. Arguments must all have units or all be "
|
||||
"unitless.");
|
||||
}
|
||||
|
||||
x = x.coerce(y.numeratorUnits, y.denominatorUnits);
|
||||
var xValue = _fuzzyRoundIfZero(x.value);
|
||||
var yValue = _fuzzyRoundIfZero(y.value);
|
||||
var atan2 = math.atan2(yValue, xValue) * 180 / math.pi;
|
||||
return SassNumber.withUnits(atan2, numeratorUnits: ['deg']);
|
||||
});
|
||||
|
||||
final _cos = _function("cos", r"$number", (arguments) {
|
||||
var number = _coerceToRad(arguments[0].assertNumber("number"));
|
||||
return SassNumber(math.cos(number.value));
|
||||
});
|
||||
|
||||
final _sin = _function("sin", r"$number", (arguments) {
|
||||
var number = _coerceToRad(arguments[0].assertNumber("number"));
|
||||
var numberValue = _fuzzyRoundIfZero(number.value);
|
||||
return SassNumber(math.sin(numberValue));
|
||||
});
|
||||
|
||||
final _tan = _function("tan", r"$number", (arguments) {
|
||||
var number = _coerceToRad(arguments[0].assertNumber("number"));
|
||||
var asymptoteInterval = 0.5 * math.pi;
|
||||
var tanPeriod = 2 * math.pi;
|
||||
if (fuzzyEquals((number.value - asymptoteInterval) % tanPeriod, 0)) {
|
||||
return SassNumber(double.infinity);
|
||||
} else if (fuzzyEquals((number.value + asymptoteInterval) % tanPeriod, 0)) {
|
||||
return SassNumber(double.negativeInfinity);
|
||||
} else {
|
||||
var numberValue = _fuzzyRoundIfZero(number.value);
|
||||
return SassNumber(math.tan(numberValue));
|
||||
}
|
||||
});
|
||||
|
||||
///
|
||||
/// Unit functions
|
||||
///
|
||||
|
||||
final _compatible = _function("compatible", r"$number1, $number2", (arguments) {
|
||||
var number1 = arguments[0].assertNumber("number1");
|
||||
var number2 = arguments[1].assertNumber("number2");
|
||||
return SassBoolean(number1.isComparableTo(number2));
|
||||
});
|
||||
|
||||
final _isUnitless = _function("is-unitless", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
return SassBoolean(!number.hasUnits);
|
||||
});
|
||||
|
||||
final _unit = _function("unit", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
return SassString(number.unitString, quotes: true);
|
||||
});
|
||||
|
||||
///
|
||||
/// Other functions
|
||||
///
|
||||
|
||||
final _percentage = _function("percentage", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
number.assertNoUnits("number");
|
||||
return SassNumber(number.value * 100, '%');
|
||||
});
|
||||
|
||||
final _random = math.Random();
|
||||
|
||||
final _randomFunction = _function("random", r"$limit: null", (arguments) {
|
||||
if (arguments[0] == sassNull) return SassNumber(_random.nextDouble());
|
||||
var limit = arguments[0].assertNumber("limit").assertInt("limit");
|
||||
@ -71,21 +313,23 @@ final _randomFunction = _function("random", r"$limit: null", (arguments) {
|
||||
return SassNumber(_random.nextInt(limit) + 1);
|
||||
});
|
||||
|
||||
final _unit = _function("unit", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
return SassString(number.unitString, quotes: true);
|
||||
});
|
||||
///
|
||||
/// Helpers
|
||||
///
|
||||
|
||||
final _isUnitless = _function("is-unitless", r"$number", (arguments) {
|
||||
var number = arguments[0].assertNumber("number");
|
||||
return SassBoolean(!number.hasUnits);
|
||||
});
|
||||
num _fuzzyRoundIfZero(num number) {
|
||||
if (!fuzzyEquals(number, 0)) return number;
|
||||
return number.isNegative ? -0.0 : 0;
|
||||
}
|
||||
|
||||
final _compatible = _function("compatible", r"$number1, $number2", (arguments) {
|
||||
var number1 = arguments[0].assertNumber("number1");
|
||||
var number2 = arguments[1].assertNumber("number2");
|
||||
return SassBoolean(number1.isComparableTo(number2));
|
||||
});
|
||||
SassNumber _coerceToRad(SassNumber number) {
|
||||
try {
|
||||
return number.coerce(['rad'], []);
|
||||
} on SassScriptException catch (error) {
|
||||
if (!error.message.startsWith('Incompatible units')) rethrow;
|
||||
throw SassScriptException('\$number: Expected ${number} to be an angle.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [Callable] named [name] that transforms a number's value
|
||||
/// using [transform] and preserves its units.
|
||||
@ -98,7 +342,7 @@ BuiltInCallable _numberFunction(String name, num transform(num value)) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Like [new BuiltInCallable.function], but always sets the URL to `sass:math`.
|
||||
/// Like [new _function.function], but always sets the URL to `sass:math`.
|
||||
BuiltInCallable _function(
|
||||
String name, String arguments, Value callback(List<Value> arguments)) =>
|
||||
BuiltInCallable.function(name, arguments, callback, url: "sass:math");
|
||||
|
@ -17,19 +17,22 @@ class BuiltInModule<T extends AsyncCallable> implements Module<T> {
|
||||
final Uri url;
|
||||
final Map<String, T> functions;
|
||||
final Map<String, T> mixins;
|
||||
final Map<String, Value> variables;
|
||||
|
||||
List<Module<T>> get upstream => const [];
|
||||
Map<String, Value> get variables => const {};
|
||||
Map<String, AstNode> get variableNodes => const {};
|
||||
Extender get extender => Extender.empty;
|
||||
CssStylesheet get css => CssStylesheet.empty(url: url);
|
||||
bool get transitivelyContainsCss => false;
|
||||
bool get transitivelyContainsExtensions => false;
|
||||
|
||||
BuiltInModule(String name, {Iterable<T> functions, Iterable<T> mixins})
|
||||
BuiltInModule(String name,
|
||||
{Iterable<T> functions, Iterable<T> mixins, Map<String, Value> variables})
|
||||
: url = Uri(scheme: "sass", path: name),
|
||||
functions = _callableMap(functions),
|
||||
mixins = _callableMap(mixins);
|
||||
mixins = _callableMap(mixins),
|
||||
variables =
|
||||
variables == null ? const {} : UnmodifiableMapView(variables);
|
||||
|
||||
/// Returns a map from [callables]' names to their values.
|
||||
static Map<String, T> _callableMap<T extends AsyncCallable>(
|
||||
@ -40,7 +43,10 @@ class BuiltInModule<T extends AsyncCallable> implements Module<T> {
|
||||
{for (var callable in callables) callable.name: callable}));
|
||||
|
||||
void setVariable(String name, Value value, AstNode nodeWithSpan) {
|
||||
throw SassScriptException("Undefined variable.");
|
||||
if (!variables.containsKey(name)) {
|
||||
throw SassScriptException("Undefined variable.");
|
||||
}
|
||||
throw SassScriptException("Cannot modify built-in variable.");
|
||||
}
|
||||
|
||||
Module<T> cloneCss() => this;
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.24.5
|
||||
version: 1.25.0
|
||||
description: A Sass implementation in Dart.
|
||||
author: Sass Team
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
Loading…
Reference in New Issue
Block a user