| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- // Utilities to parse params supplied as a map. Values can be defined in terms of
- // other values, with modifications. For example:
- //
- // @include ag-register-params((
- // a: ag-derived(b, $times: c, $plus: 2),
- // b: 4,
- // c: 10
- // ));
- // @debug ag-param(a); // outputs 42
- // Define a derived parameter. Derived values are lazily evaluated. This function is
- // sugar for defining a data structure to record the derived value's parameters.
- @function ag-derived(
- $reference-name,
- $times: null,
- $divide: null,
- $plus: null,
- $minus: null,
- $opacity: null,
- $lighten: null,
- $darken: null,
- $mix: null,
- $self-overlay: null
- ) {
- $derived: (
- "--ag-is-derived-value": true,
- "reference-name": $reference-name
- );
- @if $times != null {
- $derived: map-merge($derived, ("times": $times));
- }
- @if $divide != null {
- $derived: map-merge($derived, ("divide": $divide));
- }
- @if $plus != null {
- $derived: map-merge($derived, ("plus": $plus));
- }
- @if $minus != null {
- $derived: map-merge($derived, ("minus": $minus));
- }
- @if $opacity != null {
- $derived: map-merge($derived, ("opacity": $opacity));
- }
- @if $lighten != null {
- $derived: map-merge($derived, ("lighten": $lighten));
- }
- @if $darken != null {
- $derived: map-merge($derived, ("darken": $darken));
- }
- @if $mix != null {
- $derived: map-merge($derived, ("mix": $mix));
- }
- @if $self-overlay != null {
- $derived: map-merge($derived, ("self-overlay": $self-overlay));
- }
- @return $derived;
- }
- // Use a parameter in SCSS, e.g. `color: ag-param(foreground-color)`
- // Note, it is not possible to use this for color params, use the ag-color-property mixin instead
- @function ag-param($name, $caller: null) {
- @if $-ag-allow-color-param-access-with-ag-param != true and str-index($name, "-color") and $caller != "permitted internal _ag-theme-params.scss access" {
- @error "Illegal call to ag-param(#{$name}) - all colour params must be accessed through the ag-color-property mixin.";
- }
- $resolved: -ag-param-unchecked($name);
- @if str-index(inspect($resolved), "ag-derived(") != null {
- @error "#{$name} param contains a ag-derived() as a CSS function call expression. This means that you have tried to use ag-derived() before the function is defined - you need to include the file that defines it.";
- }
- @if type-of($resolved) == map {
- @error "ag-param(#{$name}) resolved to a map, which is not valid CSS: #{inspect($resolved)}";
- }
- @each $part in $resolved {
- @if type-of($part) == map {
- @error "ag-param(#{$name}) resolved to a list containing a map, which is not valid CSS: #{str-slice(inspect($resolved), 0, 1000)}";
- }
- }
- @return $resolved;
- }
- // Return true if a param has a value other than null or false
- @function ag-param-is-set($name) {
- $value: -ag-param-unchecked($name);
- @return $value != null and $value != false;
- }
- // Return true if two params have different values
- @function ag-params-are-different($name-a, $name-b) {
- @return -ag-param-unchecked($name-a) != -ag-param-unchecked($name-b);
- }
- // A mixin to apply a color to an element. This sets the value of a CSS property using a
- // theme parameter, and also emits CSS that allows the value to be overridden at runtime
- // using CSS variables. If the mixin is called like this:
- //
- // @include ag-color-property(background-color, header-background-color)
- //
- // ... and the header-background-color parameter is set to `red` then the emitted CSS will
- // be something like:
- //
- // background-color: red;
- // background-color: var(--ag-header-background-color, red);
- //
- // The optional $important argument can be used to add a CSS !important directive
- @mixin ag-color-property($property, $param, $important: false) {
- $value: ag-param($param, $caller: "permitted internal _ag-theme-params.scss access");
- $important: if($important, !important, null);
- @if $value != null {
- #{$property}: $value $important;
- }
- @if not ag-param-is-set(suppress-css-var-overrides) {
- $value-as-css-var: -ag-param-as-css-var($param);
- @if $value != $value-as-css-var {
- #{$property}: $value-as-css-var $important;
- }
- }
- }
- $-ag-allow-color-param-access-with-ag-param: true;
- @mixin ag-allow-color-param-access-with-ag-param($allow) {
- $-ag-allow-color-param-access-with-ag-param: $allow !global;
- }
- // Merge params supplied to a theme with the defaults, optionally validate, and register
- // the resulting map globally for use with ag-param()
- //
- // $params: params supplied by the derived theme
- // $defaults: values for params not in $params
- @function ag-process-theme-variables($params, $defaults) {
- $params: -ag-require-type($params, map, "$params argument to ag-process-theme-variables");
- // Derived themes can add params, and those new params would trigger validation errors when
- // passed to the base theme, so don't re-validate params that have already been validated
- @if not map-has-key($params, "--ag-already-validated") {
- @each $key in map-keys($params) {
- @if not map-has-key($defaults, $key) and str-index($key, "--internal-") != 1 {
- @warn "Unrecognised param \"#{$key}\"";
- }
- }
- }
- @if map-get($params, "icons-font-codes") and map-get($defaults, "icons-font-codes") {
- $merged-codes: map-merge(map-get($defaults, "icons-font-codes"), map-get($params, "icons-font-codes"));
- $params: map-merge($params, ("icons-font-codes": $merged-codes));
- }
- $params: map-merge($defaults, $params);
- $params: map-merge($params, ("--ag-already-validated": true));
- $-ag-params: $params !global;
- @return $params;
- }
- // global map of params used by ag-param()
- $-ag-params: null !default;
- // Register a params map globally so that it can be used by ag-param($name)
- // NOTE: Custom themes should NOT use this, use ag-process-theme-variables() instead
- @mixin ag-register-params($params) {
- $params: -ag-require-type($params, "map", "$params argument");
- $-ag-params: $params !global;
- }
- //
- // PRIVATE IMPLEMENTATION FUNCTIONS
- //
- // Return a parameter value as a CSS variable declaration
- @function -ag-param-as-css-var($name) {
- $value: map-get($-ag-params, $name);
- @if -is-ag-derived($value) {
- $has-modificatons: length($value) > 2;
- @if $has-modificatons {
- $value: ag-param($name, $caller: "permitted internal _ag-theme-params.scss access");
- }
- @else {
- $reference-name: map-get($value, "reference-name");
- $value: -ag-param-as-css-var($reference-name);
- }
- }
- @if $value == null {
- @return var(--ag-#{$name});
- }
- @return var(--ag-#{$name}, #{$value});
- }
- // Get a parameter, with no checks other than that the parameter exists
- @function -ag-param-unchecked($name) {
- @if $-ag-params == null {
- @error "ag-param() called before ag-register-params";
- }
- @if str-index($name, "--internal-") == 1 {
- // internal vars are returned without ag-derived resolution or validation that the var exists
- @return map-get($-ag-params, $name);
- }
- @if not map-has-key($-ag-params, $name) {
- @error "ag-param(#{$name}): no such parameter";
- }
- @return -ag-resolve-param-name($-ag-params, $name);
- }
- // Return true if a value is a record returned by ag-derived()
- @function -is-ag-derived($value) {
- @return type-of($value) == map and map-get($value, "--ag-is-derived-value") == true;
- }
- @function -ag-resolve-param-name($params, $name) {
- $value: map-get($params, $name);
- @return -ag-resolve-param-value($params, $value, $name);
- }
- @function -ag-resolve-param-value($params, $input-value, $context-name) {
- @if type-of($input-value) == list {
- $resolved: $input-value;
- @for $i from 1 through length($input-value) {
- $resolved: set-nth($resolved, $i, -ag-resolve-param-value($params, nth($resolved, $i), $context-name));
- }
- @return $resolved;
- }
- @if not -is-ag-derived($input-value) {
- @return $input-value;
- }
- $derived: $input-value;
- $reference-name: map-get($derived, "reference-name");
- @if not map-has-key($params, $reference-name) {
- @error "ag-derived: no such parameter \"#{$reference-name}\"";
- }
- $resolved: map-get($params, $reference-name);
- $resolved: -ag-resolve-param-value($params, $resolved, $reference-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "times", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "divide", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "plus", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "minus", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "opacity", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "mix", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "lighten", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "darken", $context-name);
- $resolved: -ag-apply-derived-operator($params, $resolved, $derived, "self-overlay", $context-name);
- @return -ag-resolve-param-value($params, $resolved, $reference-name);
- }
- @function -ag-apply-derived-operator($params, $lhs, $derived, $operator, $context-name) {
- @if $lhs == null {
- @return $lhs;
- }
- $rhs: map-get($derived, $operator);
- @if $rhs == null {
- @return $lhs;
- }
- @if -ag-is-css-var-token($lhs) {
- $reference-name: map-get($derived, "reference-name");
- @warn "Problem while calculating theme parameter `#{$context-name}: #{-ag-inspect-derived-value($derived)}`. This rule attempts to modify the color of `#{$reference-name}` using $#{$operator}, but (#{$reference-name}: #{$lhs}) is a CSS variable and can't be modified at compile time. Either set `#{$reference-name}` to a CSS color value (e.g. #ffffff) or provide a value for `#{$context-name}` that does not use $#{$operator}";
- @return null;
- }
- @if type-of($rhs) == string {
- $rhs: -ag-resolve-param-name($params, $rhs);
- }
- $operator-function: "-ag-operator-function-#{$operator}";
- @if not function-exists($operator-function) {
- @error "No such function #{$operator-function}";
- }
- @return call(get-function($operator-function), $params, $lhs, $rhs);
- }
- // return a string representation of an ag-derived value for debugging
- @function -ag-inspect-derived-value($derived) {
- @return "ag-derived("
- + map-get($derived, "reference-name")
- + if(map-get($derived, "times"), ", $times: #{map-get($derived, "times")}", "")
- + if(map-get($derived, "divide"), ", $divide: #{map-get($derived, "divide")}", "")
- + if(map-get($derived, "plus"), ", $plus: #{map-get($derived, "plus")}", "")
- + if(map-get($derived, "minus"), ", $minus: #{map-get($derived, "minus")}", "")
- + if(map-get($derived, "opacity"), ", $opacity: #{map-get($derived, "opacity")}", "")
- + if(map-get($derived, "mix"), ", $mix: #{map-get($derived, "mix")}", "")
- + if(map-get($derived, "lighten"), ", $lighten: #{map-get($derived, "lighten")}", "")
- + if(map-get($derived, "darken"), ", $darken: #{map-get($derived, "darken")}", "")
- + if(map-get($derived, "self-overlay"), ", $self-overlay: #{map-get($derived, "self-overlay")}", "")
- + ")";
- }
- @function -ag-is-css-var-token($value) {
- @return type-of($value) == string and str-index($value, "var(") != null
- }
- @function -ag-require-type($value, $expected, $context) {
- @if type-of($value) == $expected or ($expected == "map" and $value == ()) {
- @return $value;
- }
- @error "Expected #{$context} to be a #{$expected} but got a #{type-of($value)} instead (#{inspect($value)})";
- }
- @function -ag-operator-function-times($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "number", "value before $times");
- $rhs: -ag-require-type($rhs, "number", "argument to $times");
- @return $lhs * $rhs;
- }
- @function -ag-operator-function-divide($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "number", "value before $divide");
- $rhs: -ag-require-type($rhs, "number", "argument to $divide");
- @return $lhs / $rhs;
- }
- @function -ag-operator-function-plus($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "number", "value before $plus");
- $rhs: -ag-require-type($rhs, "number", "argument to $plus");
- @return $lhs + $rhs;
- }
- @function -ag-operator-function-minus($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "number", "value before $minus");
- $rhs: -ag-require-type($rhs, "number", "argument to $minus");
- @return $lhs - $rhs;
- }
- @function -ag-operator-function-opacity($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "color", "value before $opacity");
- $rhs: -ag-require-type($rhs, "number", "argument to $opacity");
- @if $rhs < 0 or $rhs > 1 {
- @error "Expected argument to $opacity to be between 0 and 1, got #{inspect($rhs)} instead.";
- }
- @return rgba($lhs, $rhs);
- }
- @function -ag-operator-function-mix($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "color", "value before $mix");
- @if length($rhs) != 2 {
- @error "Expected argument to $mix to be a 2-item array [color, percentage] but got #{inspect($rhs)}";
- }
- $color: nth($rhs, 1);
- @if type-of($color) == string {
- $color: -ag-resolve-param-name($params, $color);
- }
- $percentage: nth($rhs, 2);
- @if type-of($color) != color or type-of($percentage) != number {
- @error "Expected argument to $mix to be a 2-item array [color, number] but got [#{type-of($color)}, #{type-of($percentage)}]: #{inspect($rhs)}";
- }
- @return mix($color, $lhs, $percentage);
- }
- @function -ag-operator-function-lighten($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "color", "value before $lighten");
- $rhs: -ag-require-type($rhs, "number", "argument to $lighten");
- @if $rhs < 0 or $rhs > 100 {
- @error "Expected argument to $lighten to be between 0 and 100, got #{inspect($rhs)} instead.";
- }
- @return lighten($lhs, $rhs);
- }
- @function -ag-operator-function-darken($params, $lhs, $rhs) {
- $lhs: -ag-require-type($lhs, "color", "value before $darken");
- $rhs: -ag-require-type($rhs, "number", "argument to $darken");
- @if $rhs < 0 or $rhs > 100 {
- @error "Expected argument to $darken to be between 0 and 100, got #{inspect($rhs)} instead.";
- }
- @return darken($lhs, $rhs);
- }
- @function -ag-operator-function-self-overlay($params, $color, $times) {
- $color: -ag-require-type($color, "color", "value before $self-overlay");
- $times: -ag-require-type($times, "number", "argument to $self-overlay");
- @if $times < 0 or $times > 100 {
- @error "Expected argument to $self-overlay to be between 0 and 100, got #{inspect($times)} instead.";
- }
- $solidity: 1 - opacity($color);
- $output-solidity: 1;
- @if $times > 0 {
- @for $i from 1 through $times {
- $output-solidity: $output-solidity * $solidity;
- }
- }
- @return rgba($color, 1 - $output-solidity);
- }
|