Skip to content

Commit 24f84e2

Browse files
author
Awjin Ahn
authored
Adds built-in clamp() and hypot() (#906)
1 parent b3671c6 commit 24f84e2

File tree

3 files changed

+122
-41
lines changed

3 files changed

+122
-41
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 1.25.0
2+
3+
* Add functions to the built-in "sass:math" module.
4+
* `clamp()`: given a `$min`, $number`, and `$max` values, clamps the `$number`
5+
in between `$min` and `$max`.
6+
* `hypot()`: given *n* numbers, outputs the length of the *n*-dimensional
7+
vector that has components equal to each of the inputs.
8+
19
## 1.24.0
210

311
* Add an optional `with` clause to the `@forward` rule. This works like the

lib/src/functions/math.dart

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,60 @@ import '../module/built_in.dart';
1313
import '../util/number.dart';
1414
import '../value.dart';
1515

16-
/// A random number generator.
17-
final _random = math.Random();
18-
1916
/// The global definitions of Sass math functions.
2017
final global = UnmodifiableListView([
21-
_round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit, //
22-
_percentage,
18+
_abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round,
19+
_unit, //
20+
_compatible.withName("comparable"),
2321
_isUnitless.withName("unitless"),
24-
_compatible.withName("comparable")
2522
]);
2623

2724
/// The Sass math module.
2825
final module = BuiltInModule("math", functions: [
29-
_round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit,
30-
_isUnitless, //
31-
_percentage, _compatible
26+
_abs, _ceil, _clamp, _compatible, _floor, _hypot, _isUnitless, _max, _min, //
27+
_percentage, _randomFunction, _round, _unit,
3228
]);
3329

34-
final _percentage = BuiltInCallable("percentage", r"$number", (arguments) {
35-
var number = arguments[0].assertNumber("number");
36-
number.assertNoUnits("number");
37-
return SassNumber(number.value * 100, '%');
38-
});
30+
/// Returns a [Callable] named [name] that transforms a number's value
31+
/// using [transform] and preserves its units.
32+
BuiltInCallable _numberFunction(String name, num transform(num value)) {
33+
return BuiltInCallable(name, r"$number", (arguments) {
34+
var number = arguments[0].assertNumber("number");
35+
return SassNumber.withUnits(transform(number.value),
36+
numeratorUnits: number.numeratorUnits,
37+
denominatorUnits: number.denominatorUnits);
38+
});
39+
}
40+
41+
///
42+
/// Bounding functions
43+
///
3944
40-
final _round = _numberFunction("round", fuzzyRound);
4145
final _ceil = _numberFunction("ceil", (value) => value.ceil());
46+
47+
final _clamp = BuiltInCallable("clamp", r"$min, $number, $max", (arguments) {
48+
var min = arguments[0].assertNumber("min");
49+
var number = arguments[1].assertNumber("number");
50+
var max = arguments[2].assertNumber("max");
51+
52+
if (min.hasUnits == number.hasUnits && number.hasUnits == max.hasUnits) {
53+
if (min.greaterThanOrEquals(max).isTruthy) return min;
54+
if (min.greaterThanOrEquals(number).isTruthy) return min;
55+
if (number.greaterThanOrEquals(max).isTruthy) return max;
56+
return number;
57+
}
58+
59+
var arg2 = min.hasUnits != number.hasUnits ? number : max;
60+
var arg2Name = min.hasUnits != number.hasUnits ? "\$number" : "\$max";
61+
var unit1 = min.hasUnits ? "has unit ${min.unitString}" : "is unitless";
62+
var unit2 = arg2.hasUnits ? "has unit ${arg2.unitString}" : "is unitless";
63+
64+
throw SassScriptException(
65+
"\$min $unit1 but $arg2Name $unit2. Arguments must all have units or all "
66+
"be unitless.");
67+
});
68+
4269
final _floor = _numberFunction("floor", (value) => value.floor());
43-
final _abs = _numberFunction("abs", (value) => value.abs());
4470

4571
final _max = BuiltInCallable("max", r"$numbers...", (arguments) {
4672
SassNumber max;
@@ -62,39 +88,86 @@ final _min = BuiltInCallable("min", r"$numbers...", (arguments) {
6288
throw SassScriptException("At least one argument must be passed.");
6389
});
6490

65-
final _randomFunction = BuiltInCallable("random", r"$limit: null", (arguments) {
66-
if (arguments[0] == sassNull) return SassNumber(_random.nextDouble());
67-
var limit = arguments[0].assertNumber("limit").assertInt("limit");
68-
if (limit < 1) {
69-
throw SassScriptException("\$limit: Must be greater than 0, was $limit.");
91+
final _round = _numberFunction("round", fuzzyRound);
92+
93+
///
94+
/// Distance functions
95+
///
96+
97+
final _abs = _numberFunction("abs", (value) => value.abs());
98+
99+
final _hypot = BuiltInCallable("hypot", r"$numbers...", (arguments) {
100+
var numbers =
101+
arguments[0].asList.map((argument) => argument.assertNumber()).toList();
102+
103+
if (numbers.isEmpty) {
104+
throw SassScriptException("At least one argument must be passed.");
70105
}
71-
return SassNumber(_random.nextInt(limit) + 1);
72-
});
73106

74-
final _unit = BuiltInCallable("unit", r"$number", (arguments) {
75-
var number = arguments[0].assertNumber("number");
76-
return SassString(number.unitString, quotes: true);
77-
});
107+
var numeratorUnits = numbers[0].numeratorUnits;
108+
var denominatorUnits = numbers[0].denominatorUnits;
109+
var subtotal = 0.0;
78110

79-
final _isUnitless = BuiltInCallable("is-unitless", r"$number", (arguments) {
80-
var number = arguments[0].assertNumber("number");
81-
return SassBoolean(!number.hasUnits);
111+
for (var i = 0; i < numbers.length; i++) {
112+
var number = numbers[i];
113+
114+
if (number.hasUnits != numbers[0].hasUnits) {
115+
var unit1 = numbers[0].hasUnits
116+
? "has unit ${numbers[0].unitString}"
117+
: "is unitless";
118+
var unit2 =
119+
number.hasUnits ? "has unit ${number.unitString}" : "is unitless";
120+
throw SassScriptException(
121+
"Argument 1 $unit1 but argument ${i + 1} $unit2. Arguments must all "
122+
"have units or all be unitless.");
123+
}
124+
125+
number = number.coerce(numeratorUnits, denominatorUnits);
126+
subtotal += math.pow(number.value, 2);
127+
}
128+
129+
return SassNumber.withUnits(math.sqrt(subtotal),
130+
numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits);
82131
});
83132

133+
///
134+
/// Unit functions
135+
///
136+
84137
final _compatible =
85138
BuiltInCallable("compatible", r"$number1, $number2", (arguments) {
86139
var number1 = arguments[0].assertNumber("number1");
87140
var number2 = arguments[1].assertNumber("number2");
88141
return SassBoolean(number1.isComparableTo(number2));
89142
});
90143

91-
/// Returns a [Callable] named [name] that transforms a number's value
92-
/// using [transform] and preserves its units.
93-
BuiltInCallable _numberFunction(String name, num transform(num value)) {
94-
return BuiltInCallable(name, r"$number", (arguments) {
95-
var number = arguments[0].assertNumber("number");
96-
return SassNumber.withUnits(transform(number.value),
97-
numeratorUnits: number.numeratorUnits,
98-
denominatorUnits: number.denominatorUnits);
99-
});
100-
}
144+
final _isUnitless = BuiltInCallable("is-unitless", r"$number", (arguments) {
145+
var number = arguments[0].assertNumber("number");
146+
return SassBoolean(!number.hasUnits);
147+
});
148+
149+
final _unit = BuiltInCallable("unit", r"$number", (arguments) {
150+
var number = arguments[0].assertNumber("number");
151+
return SassString(number.unitString, quotes: true);
152+
});
153+
154+
///
155+
/// Other functions
156+
///
157+
158+
final _percentage = BuiltInCallable("percentage", r"$number", (arguments) {
159+
var number = arguments[0].assertNumber("number");
160+
number.assertNoUnits("number");
161+
return SassNumber(number.value * 100, '%');
162+
});
163+
164+
final _random = math.Random();
165+
166+
final _randomFunction = BuiltInCallable("random", r"$limit: null", (arguments) {
167+
if (arguments[0] == sassNull) return SassNumber(_random.nextDouble());
168+
var limit = arguments[0].assertNumber("limit").assertInt("limit");
169+
if (limit < 1) {
170+
throw SassScriptException("\$limit: Must be greater than 0, was $limit.");
171+
}
172+
return SassNumber(_random.nextInt(limit) + 1);
173+
});

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.24.0
2+
version: 1.25.0-dev
33
description: A Sass implementation in Dart.
44
author: Sass Team
55
homepage: https://github.com/sass/dart-sass

0 commit comments

Comments
 (0)