2016-08-27 11:06:15 +02:00
|
|
|
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
|
|
|
// MIT-style license that can be found in the LICENSE file or at
|
|
|
|
// https://opensource.org/licenses/MIT.
|
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
import 'dart:collection';
|
2016-09-22 00:53:54 +02:00
|
|
|
import 'dart:math' as math;
|
|
|
|
|
2016-09-22 19:32:49 +02:00
|
|
|
import 'package:collection/collection.dart';
|
|
|
|
|
2016-09-22 20:28:59 +02:00
|
|
|
import 'ast/selector.dart';
|
2016-08-27 11:06:15 +02:00
|
|
|
import 'callable.dart';
|
2016-09-20 23:59:53 +02:00
|
|
|
import 'exception.dart';
|
2016-09-22 20:45:56 +02:00
|
|
|
import 'extend/extender.dart';
|
2016-10-19 03:30:00 +02:00
|
|
|
import 'util/character.dart';
|
2016-10-15 11:57:29 +02:00
|
|
|
import 'util/number.dart';
|
2016-09-21 23:21:39 +02:00
|
|
|
import 'utils.dart';
|
2016-08-27 11:06:15 +02:00
|
|
|
import 'value.dart';
|
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// A regular expression matching the beginning of a proprietary Microsoft
|
|
|
|
/// filter declaration.
|
2016-09-21 19:43:21 +02:00
|
|
|
final _microsoftFilterStart = new RegExp(r'^[a-zA-Z]+\s*=');
|
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// Feature names supported by Dart sass.
|
2017-05-19 01:49:58 +02:00
|
|
|
final _features = new Set<String>.from([
|
2016-09-23 00:14:45 +02:00
|
|
|
"global-variable-shadowing",
|
|
|
|
"extend-selector-pseudoclass",
|
|
|
|
"units-level-3",
|
|
|
|
"at-error",
|
|
|
|
"custom-property"
|
|
|
|
]);
|
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// A random number generator.
|
2016-09-22 01:51:29 +02:00
|
|
|
final _random = new math.Random();
|
|
|
|
|
2016-09-23 01:33:22 +02:00
|
|
|
// We use base-36 so we can use the (26-character) alphabet and all digits.
|
2016-10-20 02:56:48 +02:00
|
|
|
var _uniqueID = _random.nextInt(math.pow(36, 6) as int);
|
2016-09-23 01:33:22 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
/// The functions that make up the Sass core library, indexed by name.
|
|
|
|
///
|
|
|
|
/// This excludes a few functions that need access to the evaluation context;
|
|
|
|
/// these are defined in `_EvaluateVisitor`.
|
|
|
|
final List<BuiltInCallable> coreFunctions = new UnmodifiableListView([
|
2016-09-22 19:34:06 +02:00
|
|
|
// ## Colors
|
|
|
|
// ### RGB
|
2016-09-21 17:57:53 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("rgb", r"$red, $green, $blue", (arguments) {
|
2016-12-17 03:20:03 +01:00
|
|
|
if (arguments[0].isSpecialNumber ||
|
|
|
|
arguments[1].isSpecialNumber ||
|
|
|
|
arguments[2].isSpecialNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString('rgb', arguments);
|
2016-09-25 02:46:49 +02:00
|
|
|
}
|
|
|
|
|
2016-09-20 23:59:53 +02:00
|
|
|
var red = arguments[0].assertNumber("red");
|
|
|
|
var green = arguments[1].assertNumber("green");
|
|
|
|
var blue = arguments[2].assertNumber("blue");
|
|
|
|
|
|
|
|
return new SassColor.rgb(
|
2016-09-21 23:56:56 +02:00
|
|
|
fuzzyRound(_percentageOrUnitless(red, 255, "red")),
|
|
|
|
fuzzyRound(_percentageOrUnitless(green, 255, "green")),
|
|
|
|
fuzzyRound(_percentageOrUnitless(blue, 255, "blue")));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-20 23:59:53 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable.overloaded("rgba", {
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$red, $green, $blue, $alpha": (arguments) {
|
2016-12-17 03:20:03 +01:00
|
|
|
if (arguments[0].isSpecialNumber ||
|
|
|
|
arguments[1].isSpecialNumber ||
|
|
|
|
arguments[2].isSpecialNumber ||
|
|
|
|
arguments[3].isSpecialNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString('rgba', arguments);
|
2016-09-25 02:46:49 +02:00
|
|
|
}
|
|
|
|
|
2016-09-21 01:02:26 +02:00
|
|
|
var red = arguments[0].assertNumber("red");
|
|
|
|
var green = arguments[1].assertNumber("green");
|
|
|
|
var blue = arguments[2].assertNumber("blue");
|
|
|
|
var alpha = arguments[3].assertNumber("alpha");
|
|
|
|
|
|
|
|
return new SassColor.rgb(
|
2017-05-30 02:07:21 +02:00
|
|
|
fuzzyRound(_percentageOrUnitless(red, 255, "red")),
|
|
|
|
fuzzyRound(_percentageOrUnitless(green, 255, "green")),
|
|
|
|
fuzzyRound(_percentageOrUnitless(blue, 255, "blue")),
|
2016-09-21 01:02:26 +02:00
|
|
|
_percentageOrUnitless(alpha, 1, "alpha"));
|
|
|
|
},
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$color, $alpha": (arguments) {
|
2016-09-21 01:02:26 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
2016-09-25 02:46:49 +02:00
|
|
|
|
2016-12-17 03:20:03 +01:00
|
|
|
if (arguments[1].isSpecialNumber) {
|
2016-09-25 02:46:49 +02:00
|
|
|
return new SassString(
|
|
|
|
"rgba(${color.red}, ${color.green}, ${color.blue}, "
|
2016-10-17 04:01:23 +02:00
|
|
|
"${arguments[1].toCssString()})");
|
2016-09-25 02:46:49 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 07:03:53 +02:00
|
|
|
var alpha = arguments[1].assertNumber("alpha");
|
2016-09-21 19:19:33 +02:00
|
|
|
return color.changeAlpha(_percentageOrUnitless(alpha, 1, "alpha"));
|
2016-09-21 01:02:26 +02:00
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 01:02:26 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("red", r"$color", (arguments) {
|
2016-09-21 18:30:41 +02:00
|
|
|
return new SassNumber(arguments.first.assertColor("color").red);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 17:58:02 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("green", r"$color", (arguments) {
|
2016-09-21 18:30:41 +02:00
|
|
|
return new SassNumber(arguments.first.assertColor("color").green);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 17:58:02 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("blue", r"$color", (arguments) {
|
2016-09-21 18:30:41 +02:00
|
|
|
return new SassNumber(arguments.first.assertColor("color").blue);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 17:58:02 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("mix", r"$color1, $color2, $weight: 50%", (arguments) {
|
2016-09-21 17:57:53 +02:00
|
|
|
var color1 = arguments[0].assertColor("color1");
|
|
|
|
var color2 = arguments[1].assertColor("color2");
|
|
|
|
var weight = arguments[2].assertNumber("weight");
|
2016-09-21 19:30:28 +02:00
|
|
|
return _mix(color1, color2, weight);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 17:57:53 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ### HSL
|
2016-09-21 19:01:17 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("hsl", r"$hue, $saturation, $lightness", (arguments) {
|
2016-12-17 03:20:03 +01:00
|
|
|
if (arguments[0].isSpecialNumber ||
|
|
|
|
arguments[1].isSpecialNumber ||
|
|
|
|
arguments[2].isSpecialNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString("hsl", arguments);
|
2016-09-25 02:46:49 +02:00
|
|
|
}
|
|
|
|
|
2016-09-21 19:01:17 +02:00
|
|
|
var hue = arguments[0].assertNumber("hue");
|
|
|
|
var saturation = arguments[1].assertNumber("saturation");
|
|
|
|
var lightness = arguments[2].assertNumber("lightness");
|
|
|
|
|
|
|
|
return new SassColor.hsl(hue.value, saturation.value, lightness.value);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:01:17 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("hsla", r"$hue, $saturation, $lightness, $alpha",
|
2016-09-22 17:32:59 +02:00
|
|
|
(arguments) {
|
2016-12-17 03:20:03 +01:00
|
|
|
if (arguments[0].isSpecialNumber ||
|
|
|
|
arguments[1].isSpecialNumber ||
|
|
|
|
arguments[2].isSpecialNumber ||
|
|
|
|
arguments[3].isSpecialNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString("hsla", arguments);
|
2016-09-25 02:46:49 +02:00
|
|
|
}
|
|
|
|
|
2016-09-21 19:08:46 +02:00
|
|
|
var hue = arguments[0].assertNumber("hue");
|
|
|
|
var saturation = arguments[1].assertNumber("saturation");
|
|
|
|
var lightness = arguments[2].assertNumber("lightness");
|
|
|
|
var alpha = arguments[3].assertNumber("alpha");
|
|
|
|
|
|
|
|
return new SassColor.hsl(hue.value, saturation.value, lightness.value,
|
|
|
|
_percentageOrUnitless(alpha, 1, "alpha"));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 17:32:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 17:32:59 +02:00
|
|
|
"hue",
|
|
|
|
r"$color",
|
|
|
|
(arguments) =>
|
2017-11-17 22:34:18 +01:00
|
|
|
new SassNumber(arguments.first.assertColor("color").hue, "deg")),
|
2016-09-22 17:32:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 17:32:59 +02:00
|
|
|
"saturation",
|
|
|
|
r"$color",
|
|
|
|
(arguments) =>
|
2017-11-17 22:34:18 +01:00
|
|
|
new SassNumber(arguments.first.assertColor("color").saturation, "%")),
|
2016-09-22 17:32:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 17:32:59 +02:00
|
|
|
"lightness",
|
|
|
|
r"$color",
|
|
|
|
(arguments) =>
|
2017-11-17 22:34:18 +01:00
|
|
|
new SassNumber(arguments.first.assertColor("color").lightness, "%")),
|
2016-09-22 17:32:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("adjust-hue", r"$color, $degrees", (arguments) {
|
2016-09-21 19:19:33 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var degrees = arguments[1].assertNumber("degrees");
|
|
|
|
return color.changeHsl(hue: color.hue + degrees.value);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:19:33 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("lighten", r"$color, $amount", (arguments) {
|
2016-09-21 19:22:46 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
return color.changeHsl(
|
2016-10-22 06:31:30 +02:00
|
|
|
lightness: (color.lightness + amount.valueInRange(0, 100, "amount"))
|
|
|
|
.clamp(0, 100));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:22:46 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("darken", r"$color, $amount", (arguments) {
|
2016-09-21 19:22:46 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
return color.changeHsl(
|
2016-10-22 06:31:30 +02:00
|
|
|
lightness: (color.lightness - amount.valueInRange(0, 100, "amount"))
|
|
|
|
.clamp(0, 100));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:22:46 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable.overloaded("saturate", {
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$number": (arguments) {
|
2016-12-10 01:41:47 +01:00
|
|
|
var number = arguments[0].assertNumber("number");
|
|
|
|
return new SassString("saturate(${number.toCssString()})");
|
|
|
|
},
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$color, $amount": (arguments) {
|
2016-12-10 01:41:47 +01:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
return color.changeHsl(
|
|
|
|
saturation: (color.saturation + amount.valueInRange(0, 100, "amount"))
|
|
|
|
.clamp(0, 100));
|
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:22:46 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("desaturate", r"$color, $amount", (arguments) {
|
2016-09-21 19:22:46 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
return color.changeHsl(
|
2016-10-22 06:31:30 +02:00
|
|
|
saturation: (color.saturation - amount.valueInRange(0, 100, "amount"))
|
|
|
|
.clamp(0, 100));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:22:46 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("grayscale", r"$color", (arguments) {
|
2016-09-21 19:30:28 +02:00
|
|
|
if (arguments[0] is SassNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString('grayscale', arguments);
|
2016-09-21 19:30:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
return color.changeHsl(saturation: 0);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:30:28 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("complement", r"$color", (arguments) {
|
2016-09-21 19:30:28 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
return color.changeHsl(hue: color.hue + 180);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:30:28 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("invert", r"$color, $weight: 50%", (arguments) {
|
2016-09-21 19:30:28 +02:00
|
|
|
if (arguments[0] is SassNumber) {
|
2016-10-19 02:41:49 +02:00
|
|
|
return _functionString("invert", arguments.take(1));
|
2016-09-21 19:30:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var weight = arguments[1].assertNumber("weight");
|
|
|
|
var inverse = color.changeRgb(
|
|
|
|
red: 255 - color.red, green: 255 - color.green, blue: 255 - color.blue);
|
|
|
|
if (weight.value == 50) return inverse;
|
|
|
|
|
2017-01-15 01:54:19 +01:00
|
|
|
return _mix(inverse, color, weight);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:30:28 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ### Opacity
|
2016-09-21 19:43:21 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable.overloaded("alpha", {
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$color": (arguments) {
|
2016-09-21 19:43:21 +02:00
|
|
|
var argument = arguments[0];
|
|
|
|
if (argument is SassString &&
|
|
|
|
!argument.hasQuotes &&
|
|
|
|
argument.text.contains(_microsoftFilterStart)) {
|
|
|
|
// Suport the proprietary Microsoft alpha() function.
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString("alpha", arguments);
|
2016-09-21 19:43:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var color = argument.assertColor("color");
|
|
|
|
return new SassNumber(color.alpha);
|
|
|
|
},
|
2017-11-16 22:05:48 +01:00
|
|
|
r"$args...": (arguments) {
|
2016-09-21 19:43:21 +02:00
|
|
|
if (arguments.every((argument) =>
|
|
|
|
argument is SassString &&
|
|
|
|
!argument.hasQuotes &&
|
|
|
|
argument.text.contains(_microsoftFilterStart))) {
|
|
|
|
// Suport the proprietary Microsoft alpha() function.
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString("alpha", arguments);
|
2016-09-21 19:43:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
assert(arguments.length != 1);
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 19:43:21 +02:00
|
|
|
"Only 1 argument allowed, but ${arguments.length} were passed.");
|
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 19:43:21 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("opacity", r"$color", (arguments) {
|
2016-09-21 22:49:35 +02:00
|
|
|
if (arguments[0] is SassNumber) {
|
2016-10-17 04:01:23 +02:00
|
|
|
return _functionString("opacity", arguments);
|
2016-09-21 22:49:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
return new SassNumber(color.alpha);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 22:49:35 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("opacify", r"$color, $amount", _opacify),
|
|
|
|
new BuiltInCallable("fade-in", r"$color, $amount", _opacify),
|
|
|
|
new BuiltInCallable("transparentize", r"$color, $amount", _transparentize),
|
|
|
|
new BuiltInCallable("fade-out", r"$color, $amount", _transparentize),
|
2016-09-21 22:56:23 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ### Miscellaneous
|
2016-09-21 23:21:39 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("adjust-color", r"$color, $kwargs...", (arguments) {
|
2016-09-21 23:21:39 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var argumentList = arguments[1] as SassArgumentList;
|
|
|
|
if (argumentList.contents.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:21:39 +02:00
|
|
|
"Only only positional argument is allowed. All other arguments must "
|
|
|
|
"be passed by name.");
|
|
|
|
}
|
|
|
|
|
2016-10-20 02:56:48 +02:00
|
|
|
var keywords = normalizedMap(argumentList.keywords);
|
2016-09-21 23:21:39 +02:00
|
|
|
getInRange(String name, num min, num max) =>
|
|
|
|
keywords.remove(name)?.assertNumber(name)?.valueInRange(min, max, name);
|
|
|
|
|
2017-05-30 02:07:21 +02:00
|
|
|
var red = _fuzzyRoundOrNull(getInRange("red", -255, 255));
|
|
|
|
var green = _fuzzyRoundOrNull(getInRange("green", -255, 255));
|
|
|
|
var blue = _fuzzyRoundOrNull(getInRange("blue", -255, 255));
|
2016-09-21 23:21:39 +02:00
|
|
|
var hue = keywords.remove("hue")?.assertNumber("hue")?.value;
|
|
|
|
var saturation = getInRange("saturation", -100, 100);
|
|
|
|
var lightness = getInRange("lightness", -100, 100);
|
2016-09-21 23:45:37 +02:00
|
|
|
var alpha = getInRange("alpha", -1, 1);
|
2016-09-21 23:21:39 +02:00
|
|
|
|
|
|
|
if (keywords.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:21:39 +02:00
|
|
|
"No ${pluralize('argument', keywords.length)} named "
|
|
|
|
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasRgb = red != null || green != null || blue != null;
|
|
|
|
var hasHsl = hue != null || saturation != null || lightness != null;
|
|
|
|
if (hasRgb) {
|
|
|
|
if (hasHsl) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:21:39 +02:00
|
|
|
"RGB parameters may not be passed along with HSL parameters.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return color.changeRgb(
|
2016-10-20 08:09:03 +02:00
|
|
|
red: (color.red + (red ?? 0)).clamp(0, 255) as int,
|
|
|
|
green: (color.green + (green ?? 0)).clamp(0, 255) as int,
|
|
|
|
blue: (color.blue + (blue ?? 0)).clamp(0, 255) as int,
|
|
|
|
alpha: (color.alpha + (alpha ?? 0)).clamp(0, 1));
|
2016-09-21 23:21:39 +02:00
|
|
|
} else if (hasHsl) {
|
|
|
|
return color.changeHsl(
|
|
|
|
hue: color.hue + (hue ?? 0),
|
2016-10-20 08:09:03 +02:00
|
|
|
saturation: (color.saturation + (saturation ?? 0)).clamp(0, 100),
|
|
|
|
lightness: (color.lightness + (lightness ?? 0)).clamp(0, 100),
|
2016-09-21 23:21:39 +02:00
|
|
|
alpha: color.alpha + (alpha ?? 0));
|
2016-12-19 03:54:48 +01:00
|
|
|
} else if (alpha != null) {
|
2016-10-20 08:09:03 +02:00
|
|
|
return color.changeAlpha((color.alpha + (alpha ?? 0)).clamp(0, 1));
|
2016-12-19 03:54:48 +01:00
|
|
|
} else {
|
|
|
|
return color;
|
2016-09-21 23:21:39 +02:00
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 23:21:39 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("scale-color", r"$color, $kwargs...", (arguments) {
|
2016-09-21 23:40:35 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var argumentList = arguments[1] as SassArgumentList;
|
|
|
|
if (argumentList.contents.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:40:35 +02:00
|
|
|
"Only only positional argument is allowed. All other arguments must "
|
|
|
|
"be passed by name.");
|
|
|
|
}
|
|
|
|
|
2016-10-20 02:56:48 +02:00
|
|
|
var keywords = normalizedMap(argumentList.keywords);
|
2016-09-21 23:40:35 +02:00
|
|
|
getScale(String name) {
|
|
|
|
var value = keywords.remove(name);
|
|
|
|
if (value == null) return null;
|
|
|
|
var number = value.assertNumber(name);
|
|
|
|
number.assertUnit("%", name);
|
|
|
|
return number.valueInRange(-100, 100, name) / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
scaleValue(num current, num scale, num max) {
|
|
|
|
if (scale == null) return current;
|
|
|
|
return current + (scale > 0 ? max - current : current) * scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
var red = getScale("red");
|
|
|
|
var green = getScale("green");
|
|
|
|
var blue = getScale("blue");
|
|
|
|
var saturation = getScale("saturation");
|
|
|
|
var lightness = getScale("lightness");
|
|
|
|
var alpha = getScale("alpha");
|
|
|
|
|
|
|
|
if (keywords.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:40:35 +02:00
|
|
|
"No ${pluralize('argument', keywords.length)} named "
|
|
|
|
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasRgb = red != null || green != null || blue != null;
|
|
|
|
var hasHsl = saturation != null || lightness != null;
|
|
|
|
if (hasRgb) {
|
|
|
|
if (hasHsl) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:40:35 +02:00
|
|
|
"RGB parameters may not be passed along with HSL parameters.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return color.changeRgb(
|
2017-05-30 02:07:21 +02:00
|
|
|
red: fuzzyRound(scaleValue(color.red, red, 255)),
|
|
|
|
green: fuzzyRound(scaleValue(color.green, green, 255)),
|
|
|
|
blue: fuzzyRound(scaleValue(color.blue, blue, 255)),
|
2016-09-21 23:40:35 +02:00
|
|
|
alpha: scaleValue(color.alpha, alpha, 1));
|
|
|
|
} else if (hasHsl) {
|
|
|
|
return color.changeHsl(
|
|
|
|
saturation: scaleValue(color.saturation, saturation, 100),
|
|
|
|
lightness: scaleValue(color.lightness, lightness, 100),
|
|
|
|
alpha: scaleValue(color.alpha, alpha, 1));
|
2016-12-19 03:54:48 +01:00
|
|
|
} else if (alpha != null) {
|
2016-09-21 23:40:35 +02:00
|
|
|
return color.changeAlpha(scaleValue(color.alpha, alpha, 1));
|
2016-12-19 03:54:48 +01:00
|
|
|
} else {
|
|
|
|
return color;
|
2016-09-21 23:40:35 +02:00
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 23:40:35 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("change-color", r"$color, $kwargs...", (arguments) {
|
2016-09-21 23:45:37 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var argumentList = arguments[1] as SassArgumentList;
|
|
|
|
if (argumentList.contents.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:45:37 +02:00
|
|
|
"Only only positional argument is allowed. All other arguments must "
|
|
|
|
"be passed by name.");
|
|
|
|
}
|
|
|
|
|
2016-10-20 02:56:48 +02:00
|
|
|
var keywords = normalizedMap(argumentList.keywords);
|
2016-09-21 23:45:37 +02:00
|
|
|
getInRange(String name, num min, num max) =>
|
|
|
|
keywords.remove(name)?.assertNumber(name)?.valueInRange(min, max, name);
|
|
|
|
|
2017-05-30 02:07:21 +02:00
|
|
|
var red = _fuzzyRoundOrNull(getInRange("red", 0, 255));
|
|
|
|
var green = _fuzzyRoundOrNull(getInRange("green", 0, 255));
|
|
|
|
var blue = _fuzzyRoundOrNull(getInRange("blue", 0, 255));
|
2016-09-21 23:45:37 +02:00
|
|
|
var hue = keywords.remove("hue")?.assertNumber("hue")?.value;
|
|
|
|
var saturation = getInRange("saturation", 0, 100);
|
|
|
|
var lightness = getInRange("lightness", 0, 100);
|
|
|
|
var alpha = getInRange("alpha", 0, 1);
|
|
|
|
|
|
|
|
if (keywords.isNotEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:45:37 +02:00
|
|
|
"No ${pluralize('argument', keywords.length)} named "
|
|
|
|
"${toSentence(keywords.keys.map((name) => "\$$name"), 'or')}.");
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasRgb = red != null || green != null || blue != null;
|
2016-12-19 03:54:48 +01:00
|
|
|
var hasHsl = hue != null || saturation != null || lightness != null;
|
2016-09-21 23:45:37 +02:00
|
|
|
if (hasRgb) {
|
|
|
|
if (hasHsl) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-21 23:45:37 +02:00
|
|
|
"RGB parameters may not be passed along with HSL parameters.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return color.changeRgb(red: red, green: green, blue: blue, alpha: alpha);
|
|
|
|
} else if (hasHsl) {
|
|
|
|
return color.changeHsl(
|
|
|
|
hue: hue, saturation: saturation, lightness: lightness, alpha: alpha);
|
2016-12-19 03:54:48 +01:00
|
|
|
} else if (alpha != null) {
|
2016-09-21 23:45:37 +02:00
|
|
|
return color.changeAlpha(alpha);
|
2016-12-19 03:54:48 +01:00
|
|
|
} else {
|
|
|
|
return color;
|
2016-09-21 23:45:37 +02:00
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-21 23:45:37 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("ie-hex-str", r"$color", (arguments) {
|
2016-09-22 00:15:51 +02:00
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
hexString(int component) =>
|
|
|
|
component.toRadixString(16).padLeft(2, '0').toUpperCase();
|
|
|
|
return new SassString(
|
|
|
|
"#${hexString(fuzzyRound(color.alpha * 255))}${hexString(color.red)}"
|
|
|
|
"${hexString(color.green)}${hexString(color.blue)}");
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 00:15:51 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ## Strings
|
2016-09-22 00:18:06 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("unquote", r"$string", (arguments) {
|
2016-09-22 00:18:06 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
if (!string.hasQuotes) return string;
|
|
|
|
return new SassString(string.text);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 00:18:06 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("quote", r"$string", (arguments) {
|
2016-09-22 00:18:06 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
if (string.hasQuotes) return string;
|
|
|
|
return new SassString(string.text, quotes: true);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 00:18:06 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("str-length", r"$string", (arguments) {
|
2016-09-22 00:25:01 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
return new SassNumber(string.text.runes.length);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 00:25:01 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("str-insert", r"$string, $insert, $index", (arguments) {
|
2016-09-22 00:53:54 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
var insert = arguments[1].assertString("insert");
|
|
|
|
var index = arguments[2].assertNumber("index");
|
|
|
|
index.assertNoUnits("index");
|
|
|
|
|
2016-12-30 02:14:36 +01:00
|
|
|
var indexInt = index.assertInt("index");
|
|
|
|
var codepointIndex = _codepointForIndex(indexInt, string.text.runes.length,
|
|
|
|
allowNegative: true);
|
|
|
|
|
|
|
|
// str-insert has unusual behavior for negative inputs. It guarantees that
|
|
|
|
// the $insert is at $index in the result, which means that we want to
|
|
|
|
// insert before that point if $index is positive and after if it's
|
|
|
|
// negative.
|
|
|
|
if (indexInt < 0) {
|
|
|
|
if (codepointIndex < 0) {
|
|
|
|
codepointIndex = 0;
|
|
|
|
} else {
|
|
|
|
codepointIndex++;
|
|
|
|
}
|
|
|
|
}
|
2016-12-19 02:54:35 +01:00
|
|
|
|
2016-12-30 02:14:36 +01:00
|
|
|
var codeUnitIndex =
|
|
|
|
codepointIndexToCodeUnitIndex(string.text, codepointIndex);
|
2016-09-22 00:53:54 +02:00
|
|
|
return new SassString(
|
2016-09-22 01:21:48 +02:00
|
|
|
string.text.replaceRange(codeUnitIndex, codeUnitIndex, insert.text),
|
2016-09-22 00:53:54 +02:00
|
|
|
quotes: string.hasQuotes);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 00:53:54 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("str-index", r"$string, $substring", (arguments) {
|
2016-09-22 01:04:15 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
var substring = arguments[1].assertString("substring");
|
|
|
|
|
|
|
|
var codeUnitIndex = string.text.indexOf(substring.text);
|
|
|
|
if (codeUnitIndex == -1) return sassNull;
|
2016-10-19 05:55:16 +02:00
|
|
|
var codepointIndex =
|
2016-09-22 01:04:15 +02:00
|
|
|
codeUnitIndexToCodepointIndex(string.text, codeUnitIndex);
|
2016-10-19 05:55:16 +02:00
|
|
|
return new SassNumber(codepointIndex + 1);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:04:15 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("str-slice", r"$string, $start-at, $end-at: -1",
|
2016-09-22 17:32:59 +02:00
|
|
|
(arguments) {
|
2016-09-22 01:21:48 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
|
|
|
var start = arguments[1].assertNumber("start-at");
|
|
|
|
var end = arguments[2].assertNumber("end-at");
|
2016-09-22 01:51:29 +02:00
|
|
|
start.assertNoUnits("start");
|
|
|
|
end.assertNoUnits("end");
|
2016-09-22 01:21:48 +02:00
|
|
|
|
|
|
|
var lengthInCodepoints = string.text.runes.length;
|
2017-01-15 07:39:28 +01:00
|
|
|
|
|
|
|
// No matter what the start index is, an end index of 0 will produce an
|
|
|
|
// empty string.
|
|
|
|
var endInt = end.assertInt();
|
|
|
|
if (endInt == 0) return new SassString.empty(quotes: string.hasQuotes);
|
|
|
|
|
2016-09-22 01:21:48 +02:00
|
|
|
var startCodepoint =
|
|
|
|
_codepointForIndex(start.assertInt(), lengthInCodepoints);
|
2017-01-15 07:39:28 +01:00
|
|
|
var endCodepoint =
|
|
|
|
_codepointForIndex(endInt, lengthInCodepoints, allowNegative: true);
|
2016-12-19 02:54:35 +01:00
|
|
|
if (endCodepoint == lengthInCodepoints) endCodepoint -= 1;
|
|
|
|
if (endCodepoint < startCodepoint) {
|
|
|
|
return new SassString.empty(quotes: string.hasQuotes);
|
|
|
|
}
|
|
|
|
|
2016-09-22 01:21:48 +02:00
|
|
|
return new SassString(
|
|
|
|
string.text.substring(
|
|
|
|
codepointIndexToCodeUnitIndex(string.text, startCodepoint),
|
|
|
|
codepointIndexToCodeUnitIndex(string.text, endCodepoint) + 1),
|
|
|
|
quotes: string.hasQuotes);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:21:48 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("to-upper-case", r"$string", (arguments) {
|
2016-09-22 01:24:26 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
2016-10-19 03:30:00 +02:00
|
|
|
var buffer = new StringBuffer();
|
|
|
|
for (var i = 0; i < string.text.length; i++) {
|
|
|
|
buffer.writeCharCode(toUpperCase(string.text.codeUnitAt(i)));
|
|
|
|
}
|
|
|
|
return new SassString(buffer.toString(), quotes: string.hasQuotes);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:24:26 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("to-lower-case", r"$string", (arguments) {
|
2016-09-22 01:24:26 +02:00
|
|
|
var string = arguments[0].assertString("string");
|
2016-10-19 03:30:00 +02:00
|
|
|
var buffer = new StringBuffer();
|
|
|
|
for (var i = 0; i < string.text.length; i++) {
|
|
|
|
buffer.writeCharCode(toLowerCase(string.text.codeUnitAt(i)));
|
|
|
|
}
|
|
|
|
return new SassString(buffer.toString(), quotes: string.hasQuotes);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:24:26 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ## Numbers
|
2016-09-22 01:32:27 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("percentage", r"$number", (arguments) {
|
2016-09-22 01:32:27 +02:00
|
|
|
var number = arguments[0].assertNumber("number");
|
2016-09-22 01:51:29 +02:00
|
|
|
number.assertNoUnits("number");
|
2016-09-22 01:32:27 +02:00
|
|
|
return new SassNumber(number.value * 100, '%');
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:32:27 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
_numberFunction("round", fuzzyRound),
|
|
|
|
_numberFunction("ceil", (value) => value.ceil()),
|
|
|
|
_numberFunction("floor", (value) => value.floor()),
|
|
|
|
_numberFunction("abs", (value) => value.abs()),
|
2016-09-22 01:34:48 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("max", r"$numbers...", (arguments) {
|
2016-09-22 01:41:59 +02:00
|
|
|
SassNumber max;
|
|
|
|
for (var value in arguments[0].asList) {
|
|
|
|
var number = value.assertNumber();
|
|
|
|
if (max == null || max.lessThan(number).isTruthy) max = number;
|
|
|
|
}
|
|
|
|
if (max != null) return max;
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException("At least one argument must be passed.");
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:41:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("min", r"$numbers...", (arguments) {
|
2016-09-22 01:41:59 +02:00
|
|
|
SassNumber min;
|
|
|
|
for (var value in arguments[0].asList) {
|
|
|
|
var number = value.assertNumber();
|
|
|
|
if (min == null || min.greaterThan(number).isTruthy) min = number;
|
|
|
|
}
|
|
|
|
if (min != null) return min;
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException("At least one argument must be passed.");
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:41:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("random", r"$limit: null", (arguments) {
|
2016-09-22 01:51:29 +02:00
|
|
|
if (arguments[0] == sassNull) return new SassNumber(_random.nextDouble());
|
|
|
|
var limit = arguments[0].assertNumber("limit").assertInt("limit");
|
|
|
|
if (limit < 1) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 01:51:29 +02:00
|
|
|
"\$limit: Must be greater than 0, was $limit.");
|
|
|
|
}
|
2016-10-19 03:16:55 +02:00
|
|
|
return new SassNumber(_random.nextInt(limit) + 1);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 01:51:29 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ## Lists
|
2016-09-22 17:44:09 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("length", r"$list",
|
|
|
|
(arguments) => new SassNumber(arguments[0].asList.length)),
|
2016-09-22 17:44:09 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("nth", r"$list, $n", (arguments) {
|
2016-09-22 17:47:23 +02:00
|
|
|
var list = arguments[0].asList;
|
|
|
|
var index = arguments[1].assertNumber("n");
|
|
|
|
return list[index.assertIndexFor(list, "n")];
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 17:47:23 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("set-nth", r"$list, $n, $value", (arguments) {
|
2016-09-22 18:05:50 +02:00
|
|
|
var list = arguments[0].asList;
|
|
|
|
var index = arguments[1].assertNumber("n");
|
|
|
|
var value = arguments[2];
|
|
|
|
var newList = list.toList();
|
|
|
|
newList[index.assertIndexFor(list, "n")] = value;
|
|
|
|
return arguments[0].changeListContents(newList);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 18:05:50 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 18:16:49 +02:00
|
|
|
"join", r"$list1, $list2, $separator: auto, $bracketed: auto",
|
|
|
|
(arguments) {
|
|
|
|
var list1 = arguments[0];
|
|
|
|
var list2 = arguments[1];
|
|
|
|
var separatorParam = arguments[2].assertString("separator");
|
|
|
|
var bracketedParam = arguments[3];
|
|
|
|
|
|
|
|
ListSeparator separator;
|
|
|
|
if (separatorParam.text == "auto") {
|
|
|
|
if (list1.separator != ListSeparator.undecided) {
|
|
|
|
separator = list1.separator;
|
|
|
|
} else if (list2.separator != ListSeparator.undecided) {
|
|
|
|
separator = list2.separator;
|
|
|
|
} else {
|
|
|
|
separator = ListSeparator.space;
|
|
|
|
}
|
|
|
|
} else if (separatorParam.text == "space") {
|
|
|
|
separator = ListSeparator.space;
|
|
|
|
} else if (separatorParam.text == "comma") {
|
|
|
|
separator = ListSeparator.comma;
|
|
|
|
} else {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 18:16:49 +02:00
|
|
|
'\$$separator: Must be "space", "comma", or "auto".');
|
|
|
|
}
|
|
|
|
|
|
|
|
var bracketed =
|
|
|
|
bracketedParam is SassString && bracketedParam.text == 'auto'
|
2016-09-22 18:20:32 +02:00
|
|
|
? list1.hasBrackets
|
2016-09-22 18:16:49 +02:00
|
|
|
: bracketedParam.isTruthy;
|
|
|
|
|
|
|
|
var newList = list1.asList.toList()..addAll(list2.asList);
|
2016-09-22 18:20:32 +02:00
|
|
|
return new SassList(newList, separator, brackets: bracketed);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 18:16:49 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("append", r"$list, $val, $separator: auto", (arguments) {
|
2016-09-22 18:27:01 +02:00
|
|
|
var list = arguments[0];
|
|
|
|
var value = arguments[1];
|
|
|
|
var separatorParam = arguments[2].assertString("separator");
|
|
|
|
|
|
|
|
ListSeparator separator;
|
|
|
|
if (separatorParam.text == "auto") {
|
|
|
|
separator = list.separator == ListSeparator.undecided
|
|
|
|
? ListSeparator.space
|
|
|
|
: list.separator;
|
|
|
|
} else if (separatorParam.text == "space") {
|
|
|
|
separator = ListSeparator.space;
|
|
|
|
} else if (separatorParam.text == "comma") {
|
|
|
|
separator = ListSeparator.comma;
|
|
|
|
} else {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 18:27:01 +02:00
|
|
|
'\$$separator: Must be "space", "comma", or "auto".');
|
|
|
|
}
|
|
|
|
|
|
|
|
var newList = list.asList.toList()..add(value);
|
|
|
|
return list.changeListContents(newList, separator: separator);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 18:27:01 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("zip", r"$lists...", (arguments) {
|
2016-09-22 18:40:22 +02:00
|
|
|
var lists = (arguments[0] as SassArgumentList)
|
|
|
|
.contents
|
|
|
|
.map((list) => list.asList)
|
|
|
|
.toList();
|
|
|
|
var i = 0;
|
|
|
|
var results = <SassList>[];
|
|
|
|
while (lists.every((list) => i != list.length)) {
|
|
|
|
results
|
|
|
|
.add(new SassList(lists.map((list) => list[i]), ListSeparator.space));
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return new SassList(results, ListSeparator.comma);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 18:40:22 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("index", r"$list, $value", (arguments) {
|
2016-09-22 18:45:12 +02:00
|
|
|
var list = arguments[0].asList;
|
|
|
|
var value = arguments[1];
|
|
|
|
|
|
|
|
var index = list.indexOf(value);
|
|
|
|
return index == -1 ? sassNull : new SassNumber(index + 1);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 18:45:12 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 18:45:12 +02:00
|
|
|
"list-separator",
|
|
|
|
r"$list",
|
|
|
|
(arguments) => arguments[0].separator == ListSeparator.comma
|
|
|
|
? new SassString("comma")
|
2017-11-17 22:34:18 +01:00
|
|
|
: new SassString("space")),
|
2016-09-22 18:45:12 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("is-bracketed", r"$list",
|
|
|
|
(arguments) => new SassBoolean(arguments[0].hasBrackets)),
|
2016-09-22 18:45:12 +02:00
|
|
|
|
2016-09-22 19:34:06 +02:00
|
|
|
// ## Maps
|
2016-09-22 19:17:42 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("map-get", r"$map, $key", (arguments) {
|
2016-09-22 19:17:42 +02:00
|
|
|
var map = arguments[0].assertMap("map");
|
|
|
|
var key = arguments[1];
|
|
|
|
return map.contents[key] ?? sassNull;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 19:17:42 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("map-merge", r"$map1, $map2", (arguments) {
|
2016-09-22 19:17:42 +02:00
|
|
|
var map1 = arguments[0].assertMap("map1");
|
|
|
|
var map2 = arguments[1].assertMap("map2");
|
|
|
|
return new SassMap(new Map.from(map1.contents)..addAll(map2.contents));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 19:17:42 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("map-remove", r"$map, $keys...", (arguments) {
|
2016-09-22 19:24:54 +02:00
|
|
|
var map = arguments[0].assertMap("map");
|
|
|
|
var keys = arguments[1] as SassArgumentList;
|
|
|
|
var mutableMap = new Map<Value, Value>.from(map.contents);
|
|
|
|
for (var key in keys.contents) {
|
|
|
|
mutableMap.remove(key);
|
|
|
|
}
|
|
|
|
return new SassMap(mutableMap);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 19:24:54 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 19:26:42 +02:00
|
|
|
"map-keys",
|
|
|
|
r"$map",
|
|
|
|
(arguments) => new SassList(
|
2017-11-17 22:34:18 +01:00
|
|
|
arguments[0].assertMap("map").contents.keys, ListSeparator.comma)),
|
2016-09-22 19:26:42 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable(
|
2016-09-22 19:26:42 +02:00
|
|
|
"map-values",
|
|
|
|
r"$map",
|
|
|
|
(arguments) => new SassList(
|
2017-11-17 22:34:18 +01:00
|
|
|
arguments[0].assertMap("map").contents.values, ListSeparator.comma)),
|
2016-09-22 19:26:42 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("map-has-key", r"$map, $key", (arguments) {
|
2016-09-22 19:32:49 +02:00
|
|
|
var map = arguments[0].assertMap("map");
|
|
|
|
var key = arguments[1];
|
|
|
|
return new SassBoolean(map.contents.containsKey(key));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 19:32:49 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("keywords", r"$args", (arguments) {
|
2016-09-22 19:32:49 +02:00
|
|
|
var argumentList = arguments[0];
|
|
|
|
if (argumentList is SassArgumentList) {
|
2016-10-20 02:56:48 +02:00
|
|
|
return new SassMap(mapMap(argumentList.keywords,
|
|
|
|
key: (String key, Value _) => new SassString(key)));
|
2016-09-22 19:32:49 +02:00
|
|
|
} else {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 19:32:49 +02:00
|
|
|
"\$args: $argumentList is not an argument list.");
|
|
|
|
}
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 19:32:49 +02:00
|
|
|
|
2016-09-22 20:12:19 +02:00
|
|
|
// ## Selectors
|
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-nest", r"$selectors...", (arguments) {
|
2016-09-22 20:12:19 +02:00
|
|
|
var selectors = (arguments[0] as SassArgumentList).contents;
|
|
|
|
if (selectors.isEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 20:12:19 +02:00
|
|
|
"\$selectors: At least one selector must be passed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectors
|
|
|
|
.map((selector) => selector.assertSelector(allowParent: true))
|
|
|
|
.reduce((parent, child) => child.resolveParentSelectors(parent))
|
|
|
|
.asSassList;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 20:12:19 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-append", r"$selectors...", (arguments) {
|
2016-09-22 20:28:59 +02:00
|
|
|
var selectors = (arguments[0] as SassArgumentList).contents;
|
|
|
|
if (selectors.isEmpty) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-22 20:28:59 +02:00
|
|
|
"\$selectors: At least one selector must be passed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectors
|
|
|
|
.map((selector) => selector.assertSelector())
|
|
|
|
.reduce((parent, child) {
|
|
|
|
return new SelectorList(child.components.map((complex) {
|
|
|
|
var compound = complex.components.first;
|
|
|
|
if (compound is CompoundSelector) {
|
|
|
|
var newCompound = _prependParent(compound);
|
|
|
|
if (newCompound == null) {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException("Can't append $complex to $parent.");
|
2016-09-22 20:28:59 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 02:56:48 +02:00
|
|
|
return new ComplexSelector(<ComplexSelectorComponent>[newCompound]
|
|
|
|
..addAll(complex.components.skip(1)));
|
2016-09-22 20:28:59 +02:00
|
|
|
} else {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException("Can't append $complex to $parent.");
|
2016-09-22 20:28:59 +02:00
|
|
|
}
|
|
|
|
})).resolveParentSelectors(parent);
|
|
|
|
}).asSassList;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 20:28:59 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-extend", r"$selector, $extendee, $extender",
|
|
|
|
(arguments) {
|
2016-09-22 20:45:56 +02:00
|
|
|
var selector = arguments[0].assertSelector(name: "selector");
|
2017-05-29 01:00:37 +02:00
|
|
|
var target = arguments[1].assertSelector(name: "extendee");
|
2016-09-22 20:45:56 +02:00
|
|
|
var source = arguments[2].assertSelector(name: "extender");
|
|
|
|
|
|
|
|
return Extender.extend(selector, source, target).asSassList;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 20:45:56 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-replace", r"$selector, $original, $replacement",
|
|
|
|
(arguments) {
|
2016-09-22 23:09:35 +02:00
|
|
|
var selector = arguments[0].assertSelector(name: "selector");
|
2017-05-29 01:00:37 +02:00
|
|
|
var target = arguments[1].assertSelector(name: "original");
|
2016-09-22 23:09:35 +02:00
|
|
|
var source = arguments[2].assertSelector(name: "replacement");
|
|
|
|
|
|
|
|
return Extender.replace(selector, source, target).asSassList;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 23:09:35 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-unify", r"$selector1, $selector2", (arguments) {
|
2016-09-22 23:50:25 +02:00
|
|
|
var selector1 = arguments[0].assertSelector(name: "selector1");
|
|
|
|
var selector2 = arguments[1].assertSelector(name: "selector2");
|
|
|
|
|
|
|
|
var result = selector1.unify(selector2);
|
|
|
|
return result == null ? sassNull : result.asSassList;
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 23:50:25 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("is-superselector", r"$super, $sub", (arguments) {
|
2016-09-22 23:57:34 +02:00
|
|
|
var selector1 = arguments[0].assertSelector(name: "super");
|
|
|
|
var selector2 = arguments[1].assertSelector(name: "sub");
|
|
|
|
|
|
|
|
return new SassBoolean(selector1.isSuperselector(selector2));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-22 23:57:34 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("simple-selectors", r"$selector", (arguments) {
|
2016-09-23 00:07:56 +02:00
|
|
|
var selector = arguments[0].assertCompoundSelector(name: "selector");
|
|
|
|
|
|
|
|
return new SassList(
|
|
|
|
selector.components.map((simple) => new SassString(simple.toString())),
|
|
|
|
ListSeparator.comma);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-23 00:07:56 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("selector-parse", r"$selector",
|
|
|
|
(arguments) => arguments[0].assertSelector(name: "selector").asSassList),
|
2016-09-23 00:10:02 +02:00
|
|
|
|
2016-09-21 18:30:41 +02:00
|
|
|
// ## Introspection
|
2016-09-21 17:57:53 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("feature-exists", r"$feature", (arguments) {
|
2016-09-23 00:14:45 +02:00
|
|
|
var feature = arguments[0].assertString("feature");
|
2016-10-19 02:44:05 +02:00
|
|
|
return new SassBoolean(_features.contains(feature.text));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-23 00:22:58 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("inspect", r"$value",
|
|
|
|
(arguments) => new SassString(arguments.first.toString())),
|
2016-09-23 00:30:24 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("type-of", r"$value", (arguments) {
|
2016-09-23 00:30:24 +02:00
|
|
|
var value = arguments[0];
|
|
|
|
if (value is SassArgumentList) return new SassString("arglist");
|
|
|
|
if (value is SassBoolean) return new SassString("bool");
|
|
|
|
if (value is SassColor) return new SassString("color");
|
|
|
|
if (value is SassList) return new SassString("list");
|
|
|
|
if (value is SassMap) return new SassString("map");
|
|
|
|
if (value is SassNull) return new SassString("null");
|
|
|
|
if (value is SassNumber) return new SassString("number");
|
2017-01-15 00:14:57 +01:00
|
|
|
if (value is SassFunction) return new SassString("function");
|
2016-09-23 00:30:24 +02:00
|
|
|
assert(value is SassString);
|
|
|
|
return new SassString("string");
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-23 00:34:03 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("unit", r"$number", (arguments) {
|
2016-09-23 00:34:03 +02:00
|
|
|
var number = arguments[0].assertNumber("number");
|
|
|
|
return new SassString(number.unitString, quotes: true);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-23 00:34:03 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("unitless", r"$number", (arguments) {
|
2016-09-23 00:34:03 +02:00
|
|
|
var number = arguments[0].assertNumber("number");
|
|
|
|
return new SassBoolean(!number.hasUnits);
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-09-23 00:52:53 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("comparable", r"$number1, $number2", (arguments) {
|
2016-09-23 00:52:53 +02:00
|
|
|
var number1 = arguments[0].assertNumber("number1");
|
|
|
|
var number2 = arguments[1].assertNumber("number2");
|
|
|
|
return new SassBoolean(number1.isComparableTo(number2));
|
2017-11-17 22:34:18 +01:00
|
|
|
}),
|
2016-10-29 00:28:05 +02:00
|
|
|
|
2016-10-15 23:31:21 +02:00
|
|
|
// call() is defined in _PerformVisitor to provide it access to private APIs.
|
2016-09-23 01:33:22 +02:00
|
|
|
|
|
|
|
// ## Miscellaneous
|
|
|
|
|
2016-09-24 11:52:08 +02:00
|
|
|
// This is only invoked using `call()`. Hand-authored `if()`s are parsed as
|
|
|
|
// [IfExpression]s.
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("if", r"$condition, $if-true, $if-false",
|
|
|
|
(arguments) => arguments[0].isTruthy ? arguments[1] : arguments[2]),
|
2016-09-24 11:52:08 +02:00
|
|
|
|
2017-11-17 22:34:18 +01:00
|
|
|
new BuiltInCallable("unique-id", "", (arguments) {
|
2016-09-23 01:33:22 +02:00
|
|
|
// Make it difficult to guess the next ID by randomizing the increase.
|
2016-10-19 02:23:50 +02:00
|
|
|
_uniqueID += _random.nextInt(36) + 1;
|
2016-10-20 02:56:48 +02:00
|
|
|
if (_uniqueID > math.pow(36, 6)) _uniqueID %= math.pow(36, 6) as int;
|
2016-09-23 01:33:22 +02:00
|
|
|
// The leading "u" ensures that the result is a valid identifier.
|
|
|
|
return new SassString("u${_uniqueID.toRadixString(36).padLeft(6, '0')}");
|
2017-11-17 22:34:18 +01:00
|
|
|
})
|
|
|
|
]);
|
2016-09-20 23:59:53 +02:00
|
|
|
|
2016-10-17 04:01:23 +02:00
|
|
|
/// Returns a string representation of [name] called with [arguments], as though
|
|
|
|
/// it were a plain CSS function.
|
2016-10-19 02:41:49 +02:00
|
|
|
SassString _functionString(String name, Iterable<Value> arguments) =>
|
2016-10-17 04:01:23 +02:00
|
|
|
new SassString("$name(" +
|
|
|
|
arguments.map((argument) => argument.toCssString()).join(', ') +
|
|
|
|
")");
|
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// Asserts that [number] is a percentage or has no units, and normalizes the
|
|
|
|
/// value.
|
|
|
|
///
|
|
|
|
/// If [number] has no units, its value is clamped to be greater than `0` or
|
|
|
|
/// less than [max] and returned. If [number] is a percentage, it's scaled to be
|
2016-10-17 03:46:38 +02:00
|
|
|
/// within `0` and [max]. Otherwise, this throws a [SassScriptException].
|
2016-10-10 08:51:20 +02:00
|
|
|
///
|
|
|
|
/// [name] is used to identify the argument in the error message.
|
2016-09-21 01:02:26 +02:00
|
|
|
num _percentageOrUnitless(SassNumber number, num max, String name) {
|
2016-09-20 23:59:53 +02:00
|
|
|
num value;
|
|
|
|
if (!number.hasUnits) {
|
|
|
|
value = number.value;
|
|
|
|
} else if (number.hasUnit("%")) {
|
|
|
|
value = max * number.value / 100;
|
|
|
|
} else {
|
2016-10-17 03:46:38 +02:00
|
|
|
throw new SassScriptException(
|
2016-09-20 23:59:53 +02:00
|
|
|
'\$$name: Expected $number to have no units or "%".');
|
|
|
|
}
|
|
|
|
|
2016-09-21 01:02:26 +02:00
|
|
|
return value.clamp(0, max);
|
2016-09-20 23:59:53 +02:00
|
|
|
}
|
2016-09-21 19:30:28 +02:00
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// Returns [color1] and [color2], mixed together and weighted by [weight].
|
2016-09-21 19:30:28 +02:00
|
|
|
SassColor _mix(SassColor color1, SassColor color2, SassNumber weight) {
|
|
|
|
// This algorithm factors in both the user-provided weight (w) and the
|
|
|
|
// difference between the alpha values of the two colors (a) to decide how
|
|
|
|
// to perform the weighted average of the two RGB values.
|
|
|
|
//
|
|
|
|
// It works by first normalizing both parameters to be within [-1, 1], where
|
|
|
|
// 1 indicates "only use color1", -1 indicates "only use color2", and all
|
|
|
|
// values in between indicated a proportionately weighted average.
|
|
|
|
//
|
|
|
|
// Once we have the normalized variables w and a, we apply the formula
|
|
|
|
// (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. This
|
|
|
|
// formula has two especially nice properties:
|
|
|
|
//
|
|
|
|
// * When either w or a are -1 or 1, the combined weight is also that
|
|
|
|
// number (cases where w * a == -1 are undefined, and handled as a
|
|
|
|
// special case).
|
|
|
|
//
|
|
|
|
// * When a is 0, the combined weight is w, and vice versa.
|
|
|
|
//
|
|
|
|
// Finally, the weight of color1 is renormalized to be within [0, 1] and the
|
|
|
|
// weight of color2 is given by 1 minus the weight of color1.
|
|
|
|
var weightScale = weight.valueInRange(0, 100, "weight") / 100;
|
|
|
|
var normalizedWeight = weightScale * 2 - 1;
|
|
|
|
var alphaDistance = color1.alpha - color2.alpha;
|
|
|
|
|
|
|
|
var combinedWeight1 = normalizedWeight * alphaDistance == -1
|
|
|
|
? normalizedWeight
|
|
|
|
: (normalizedWeight + alphaDistance) /
|
|
|
|
(1 + normalizedWeight * alphaDistance);
|
|
|
|
var weight1 = (combinedWeight1 + 1) / 2;
|
|
|
|
var weight2 = 1 - weight1;
|
|
|
|
|
|
|
|
return new SassColor.rgb(
|
2017-05-30 02:07:21 +02:00
|
|
|
fuzzyRound(color1.red * weight1 + color2.red * weight2),
|
|
|
|
fuzzyRound(color1.green * weight1 + color2.green * weight2),
|
|
|
|
fuzzyRound(color1.blue * weight1 + color2.blue * weight2),
|
2016-09-21 19:30:28 +02:00
|
|
|
color1.alpha * weightScale + color2.alpha * (1 - weightScale));
|
|
|
|
}
|
2016-09-21 22:56:23 +02:00
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// The definition of the `opacify()` and `fade-in()` functions.
|
2016-09-21 22:56:23 +02:00
|
|
|
SassColor _opacify(List<Value> arguments) {
|
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
|
2016-11-15 07:46:58 +01:00
|
|
|
return color.changeAlpha(
|
|
|
|
(color.alpha + amount.valueInRange(0, 1, "amount")).clamp(0, 1));
|
2016-09-21 22:56:23 +02:00
|
|
|
}
|
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// The definition of the `transparentize()` and `fade-out()` functions.
|
2016-09-21 22:56:23 +02:00
|
|
|
SassColor _transparentize(List<Value> arguments) {
|
|
|
|
var color = arguments[0].assertColor("color");
|
|
|
|
var amount = arguments[1].assertNumber("amount");
|
|
|
|
|
2016-11-15 07:46:58 +01:00
|
|
|
return color.changeAlpha(
|
|
|
|
(color.alpha - amount.valueInRange(0, 1, "amount")).clamp(0, 1));
|
2016-09-21 22:56:23 +02:00
|
|
|
}
|
2016-09-22 01:21:48 +02:00
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// Converts a Sass string index into a codepoint index into a string whose
|
|
|
|
/// [String.runes] has length [lengthInCodepoints].
|
|
|
|
///
|
2016-10-15 11:57:29 +02:00
|
|
|
/// A Sass string index is one-based, and uses negative numbers to count
|
2016-10-10 08:51:20 +02:00
|
|
|
/// backwards from the end of the string. A codepoint index is an index into
|
|
|
|
/// [String.runes].
|
2016-12-19 02:54:35 +01:00
|
|
|
///
|
|
|
|
/// If [index] is negative and it points before the beginning of
|
|
|
|
/// [lengthInCodepoints], this will return `0` if [allowNegative] is `false` and
|
|
|
|
/// the index if it's `true`.
|
|
|
|
int _codepointForIndex(int index, int lengthInCodepoints,
|
|
|
|
{bool allowNegative: false}) {
|
2016-09-22 01:21:48 +02:00
|
|
|
if (index == 0) return 0;
|
|
|
|
if (index > 0) return math.min(index - 1, lengthInCodepoints);
|
2016-12-19 02:54:35 +01:00
|
|
|
var result = lengthInCodepoints + index;
|
|
|
|
if (result < 0 && !allowNegative) return 0;
|
|
|
|
return result;
|
2016-09-22 01:21:48 +02:00
|
|
|
}
|
2016-09-22 01:34:48 +02:00
|
|
|
|
2017-11-16 22:05:48 +01:00
|
|
|
/// Returns a [Callable] named [name] that transforms a number's value
|
2016-10-10 08:51:20 +02:00
|
|
|
/// using [transform] and preserves its units.
|
2017-11-17 22:34:18 +01:00
|
|
|
BuiltInCallable _numberFunction(String name, num transform(num value)) {
|
2016-09-22 01:34:48 +02:00
|
|
|
return new BuiltInCallable(name, r"$number", (arguments) {
|
|
|
|
var number = arguments[0].assertNumber("number");
|
|
|
|
return new SassNumber.withUnits(transform(number.value),
|
|
|
|
numeratorUnits: number.numeratorUnits,
|
|
|
|
denominatorUnits: number.denominatorUnits);
|
|
|
|
});
|
|
|
|
}
|
2016-09-22 20:28:59 +02:00
|
|
|
|
2016-10-10 08:51:20 +02:00
|
|
|
/// Adds a [ParentSelector] to the beginning of [compound], or returns `null` if
|
|
|
|
/// that wouldn't produce a valid selector.
|
2016-09-22 20:28:59 +02:00
|
|
|
CompoundSelector _prependParent(CompoundSelector compound) {
|
|
|
|
var first = compound.components.first;
|
|
|
|
if (first is UniversalSelector) return null;
|
|
|
|
if (first is TypeSelector) {
|
|
|
|
if (first.name.namespace != null) return null;
|
2016-10-20 02:56:48 +02:00
|
|
|
return new CompoundSelector(<SimpleSelector>[
|
|
|
|
new ParentSelector(suffix: first.name.name)
|
|
|
|
]..addAll(compound.components.skip(1)));
|
2016-09-22 20:28:59 +02:00
|
|
|
} else {
|
|
|
|
return new CompoundSelector(
|
2016-10-01 01:41:22 +02:00
|
|
|
<SimpleSelector>[new ParentSelector()]..addAll(compound.components));
|
2016-09-22 20:28:59 +02:00
|
|
|
}
|
|
|
|
}
|
2017-05-30 02:07:21 +02:00
|
|
|
|
|
|
|
/// Like [fuzzyRound], but returns `null` if [number] is `null`.
|
|
|
|
int _fuzzyRoundOrNull(num number) => number == null ? null : fuzzyRound(number);
|